領域驅動設計(DDD)實踐之路(四):領域驅動在微服務設計中的應用


這是“領域驅動設計實踐之路”系列的第四篇文章,從單體架構的弊端引入微服務,結合領域驅動的概念介紹了如何做微服務划分、設計領域模型並展示了整體的微服務化的系統架構設計。結合分層架構、六邊形架構和整潔架構的思想,以實際使用場景為背景,展示了一個微服務的程序結構設計。

一、單體架構的弊端

單體結構示例(引用自互聯網)

一般在業務發展的初期,整個應用涉及的功能需求較少,相對比較簡單,單體架構的應用比較容易部署、測試,橫向擴展也比較易實現。

然而,隨着需求的不斷增加, 越來越多的人加入開發團隊,代碼庫也在飛速地膨脹。慢慢地,單體應用變得越來越臃腫,可維護性、靈活性逐漸降低,維護成本越來越高。

下面分析下單體架構應用存在的一些弊端:

1、復雜性高

在項目初期應該有人可以做到對應用各個功能和實現了如指掌,隨着業務需求的增多,各種業務流程錯綜復雜的揉在一起,整個系統變得龐大且復雜,以至於很少有開發者清楚每一個功能和業務流程細節。

這樣會使得新業務的需求評估或者異常問題定位會占用較多的時間,同時也蘊含着未知風險。更糟糕的是,這種極度的復雜性會形成一種惡性循環,每一次更改都會使得系統變得更復雜,更難懂。

2.技術債務多

隨着時間推移、需求變更和人員更迭,會逐漸形成應用程序的技術債務,並且越積越多。比如,團隊必須長期使用一套相同的技術棧,很難采用新的框架和編程語言。有時候想引入一些新的工具時,就會使得項目中需要同時維護多套技術框架,比如同時維護Hibernate和Mybatis,使得成本變高。

3.錯誤難隔離

由於業務項目的所有功能模塊都在一個應用上承擔,包括核心和非核心模塊,任何一個模塊或者一個小細節的地方,因為設計不合理、代碼質量差等原因,都有可能造成應用實例的崩潰,從而使得業務全面受到影響。其根本原因就是核心和非核心功能的代碼都運行在同一個環境中。

4. 項目團隊間協同成本高,業務響應越來越慢

多個類似的業務項目之間勢必會存在類似的功能模塊,如果都采用單體模式,就會帶來重復功能建設和維護。而且,有時候還需要互相產生交互,打通單體系統之間的交互集成和協作的成本也需要額外付出。

再者,當項目大到一定程度,不同的模塊可能是不同的團隊來維護,迭代聯調的沖突,代碼合並分支的沖突都會影響整個開發進度,從而使得業務響應速度越來越慢。

5.擴展成本高

隨着業務的發展,系統在出現業務處理瓶頸的時候,往往是由於某一個或幾個功能模塊負載較高造成的,但因為所有功能都打包在一起,在出現此類問題時,只能通過增加應用實例的方式分擔負載,沒辦法對單獨的幾個功能模塊進行服務能力的擴展,從而帶來資源額外配置的消耗,成本較高。

針對以上痛點,近年來越來越多的互聯網公司采用“微服務”架構構建自身的業務平台,而“微服務”也獲得了越來越多技術人員的肯定。

微服務其實是SOA的一種演變后的形態,與SOA的方法和原則沒有本質區別。SOA理念的核心價值是,松耦合的服務帶來業務的復用,按照業務而不是技術的維度,結合高內聚、低耦合的原則來划分微服務,這正好與領域驅動設計所倡導的理念相契合。

二、微服務設計

1. 微服務划分

從廣義上講,領域即是一個組織所做的事情以及其中包含的一切。每個組織都有它自己的業務范圍和做事方式,這個業務范圍以及在其中所進行的活動便是領域。

DDD的子域和限界上下文的概念,可以很好地跟微服務架構中的服務進行匹配。而且,微服務架構中的自治化團隊負責服務開發的概念,也與DDD中每個領域模型都由一個獨立團隊負責開發的概念吻合。DDD倡導按業務領域來划分系統,微服務架構更強調從業務維度去做分治來應對系統復雜度,跳過業務架構設計出來的架構關注點不在業務響應上,可能就是個大泥球,在面臨需求迭代或響應市場變化時就很痛苦。

DDD的核心訴求就是將業務架構映射到系統架構上,在響應業務變化調整業務架構時,也隨之變化系統架構。而微服務追求業務層面的復用,設計出來的系統架構和業務一致;在技術架構上則系統模塊之間充分解耦,可以自由地選擇合適的技術架構,去中心化地治理技術和數據。

以電商的資源訂購系統為例,典型業務用例場景包括查看資源,購買資源,查詢用戶已購資源等。

領域驅動為每一個子域定義單獨的領域模型,子域是領域的一部分,從業務的角度分析我們需要覆蓋的業務用例場景,以高內聚低耦合的思想,結合單一職責原則(SRP)和閉包原則(CCP),從業務領域的角度,划分出用戶管理子域,資源管理子域,訂單子域和支付子域共四個子域。

每個子域對應一個限界上下文。限界上下文是一種概念上的邊界,領域模型便工作於其中,每個限界上下文都有自己的通用語言。限界上下文使得你在領域模型周圍加上了一個顯式的、清晰的邊界。當然,限界上下文不僅僅包含領域模型。當使用微服務架構時,每個限界上下文對應一個微服務。

2. 領域模型

聚合是一個邊界內領域對象的集群,可以將其視為一個單元,它由根實體和可能的一個或多個其他實體和值對象組成。聚合將領域模型分解為,每個聚合都可以作為一個單元進行處理。

聚合根是聚合中唯一可以由外部類引用的部分,客戶端只能通過調用聚合根上的方法來更新聚合。

聚合代表了一致的邊界,對於一個設計良好的聚合來說,無論由於何種業務需求而發生改變,在單個事務中,聚合中的所有不變條件都是一致的。聚合的一個很重要的經驗設計原則是,一個事務中只修改一個聚合實例。更新聚合時需要更新整個聚合而不是聚合中的一部分,否則容易產生一致性問題。

比如A和B同時在網上購買東西,使用同一張訂單,同時意識到自己購買的東西超過預算,此時A減少點心數量,B減少面包數量,兩個消費者並發執行事務,那么訂單總額可能會低於最低訂單限額要求,但對於一個消費者來說是滿足最低限額要求的。所以應該站在聚合根的角度執行更新操作,這會強制執行一致性業務規則。

另外,我們不應該設計過大的聚合,處理大聚合構成的"巨無霸"對象時,容易出現不同用例同時需要修改其中的某個部分,因為聚合設計時考慮的一致性約束是對整個聚合產生作用的,所以對聚合的修改會造成對聚合整體的變更,如果采用樂觀並發,這樣就容易產生某些用例會被拒絕的場景,而且還會影響系統的性能和可伸縮性。

使用大聚合時,往往為了完成一項基本操作,需要將成百上千個對象一同加載到內存中,造成資源的浪費。所以應盡量采用小聚合,一方面使用根實體來表示聚合,其中只包含最小數量的屬性或值類型屬性,這里的最小數量表示所需的最小屬性集合,不多也不少。必須與其他屬性保持一致的屬性是所需的屬性。

在聚合中,如果你認為有些被包含部分應該建模成一個實體,此時,思考下這個部分是否會隨着時間而改變,或者該部分是否能被全部替換。如果可以全部替換,那么可以建模成值對象,而非實體。因為值對象本身是不可變的,只能進行全部替換,使用起來更安全,所以,一般情況下優先使用值對象。很多情況下,許多建模成實體的概念都可以重構成值對象。小聚合還有助於事務的成功執行,即它可以減少事務提交沖突,這樣不僅可以提升系統的性能和可伸縮性,另外系統的可用性也得到了增強。

另外聚合直接的引用通過唯一標識實現,而不是通過對象引用,這樣不僅減少聚合的使用空間,更重要的是可以實現聚合直接的松耦合。如果聚合是另一個服務的一部分,則不會出現跨服務的對象引用問題,當然在聚合內部對象之間是可以相互引用的。

上述關於聚合的主要使用原則總結起來可以歸納為以下幾點:

  1. 只引用聚合根。
  2.  通過唯一標識引用其他聚合。
  3. 一個事務中只能創建或修改一個聚合。
  4. 聚合邊界之外使用最終一致性。

當然在實際使用的過程中,比如某一個業務用例需要獲取到聚合中的某個領域對象,但該領域對象的獲取路徑較繁瑣,為了兼容該特殊場景,可以將聚合中的屬性(實體或值對象)直接返回給應用層,使得應用層直接操作該領域對象。

我們經常會遇到在一個聚合上執行命令方法時,還需要在其他聚合上執行額外的業務規則,盡量使用最終一致性,因為最終一致性可以按聚合維度分步驟處理各個環節,從而提升系統的吞吐量。對於一個業務用例,如果應該由執行該用例的用戶來保證數據的一致性,那么可以考慮使用事務一致性,當然此時依然需要遵循其他聚合原則。如果需要其他用戶或者系統來保證數據一致性,那么使用最終一致性。實際上,最終一致性可以支持絕大部分的業務場景。

基於上面對電商的資源訂購系統業務子域的划分,設計出資源聚合,訂單聚合,支付聚合和用戶聚合,資源聚合與訂單聚合之間通過資源ID進行關聯,訂單聚合與支付聚合之間通過訂單ID和用戶ID進行關聯,支付聚合和用戶聚合之間通過用戶ID進行關聯。資源聚合根中包含多個資源包值對象,一個資源包值對象又包含多個預覽圖值對象。當然在實際開發的過程中,根據實際情況聚合根中也可以包含實體對象。每個聚合對應一個微服務,對於特別復雜的系統,一個子域可能包含多個聚合,也就包含多個微服務。

3. 微服務系統架構設計

 

基於上面對電商的資源訂購系統子域的分析,服務器后台使用用戶服務,資源服務,訂單服務和支付服務四個微服務實現。上圖中的API Gateway也是一種服務,同時可以看成是DDD中的應用層,類似面向對象設計中的外觀(Facade)模式。

作為整個后端架構的統一門面,封裝了應用程序內部架構,負責業務用例的任務協調,每個用例對應了一個服務方法,調用多個微服務並將聚合結果返回給客戶端。它還可能有其他職責,比如身份驗證,訪問授權,緩存,速率限制等。以查詢已購資源為例,API Gateway需要查詢訂單服務獲取當前用戶已購的資源ID列表,然后根據資源ID列表查詢資源服務獲取已購資源的詳細信息,最終將聚合結果返回給客戶端。

當然在實際應用的過程中,我們也可以根據API請求的復雜度,從業務角度,將API Gateway划分為多個不同的服務,防止又回歸到API Gateway的單體瓶頸。

另外,有時候從業務領域角度划分出來的某些子域比較小,從資源利用率的角度,單獨放到一個微服務中有點單薄。這個時候我們可以打破一個限界上下文對應一個微服務的理念,將多個子域合並到同一個微服務中,由微服務自己的應用層實現多子域任務的協調。

所以,在我們的系統架構中可能會出現微服務級別的小應用層和API Gateway級別的大應用層使用場景,理論固然是理論,還是需要結合實際情況靈活應用。

三、領域驅動概念在單個微服務設計中的應用

1. 架構選擇分析

分層架構圖(引用自互聯網)

六邊形架構圖(引用自互聯網)

整潔架構圖(引用自互聯網)

上面整潔架構圖中的同心圓分別代表了軟件系統中的不同層次,通常越靠近中心,其所在的軟件層次就越高。

整潔架構的依賴關系規則告訴我們,源碼中的依賴關系必須只指向同心圓的內層,即由低層機制指向高層策略。換句話說,任何屬於內層圓中的代碼都不應該牽涉外層圓中的代碼,尤其是內層圓中的代碼不應該引用外層圓中代碼所聲明的名字,包括函數、類、變量以及一切其他有命名的軟件實體。同樣,外層圓使用的數據格式也不應該被內層圓中的代碼所使用,尤其是當數據格式由外層圓的框架所生成時。

總之,不應該讓外層圓中發生的任何變更影響到內層圓的代碼。業務實體這一層封裝的是整個業務領域中最通用、最高層的業務邏輯,它們應該屬於系統中最不容易受外界影響而變動的部分,也就是說一般情況下我們的核心領域模型部分是比較穩定的,不應該因為外層的基礎設施比如數據存儲技術選型的變化,或者UI展示方式等的變化受影響,從而需要做相應的改動。

在以往的項目經驗中,大多數同學習慣也比較熟悉分層架構,一般包括展示層、應用層,領域層和基礎設施層。六邊形架構的一個重要好處是它將業務邏輯與適配器中包含的表示層和數據訪問層的邏輯分離開來,業務邏輯不依賴於表示層邏輯或數據訪問層邏輯,由於這種分離,單獨測試業務邏輯要容易得多。

另一個好處是,可以通過多個適配器調用業務邏輯,每個適配器實現特定的API或用戶界面。業務邏輯還可以調用多個適配器,每個適配器調用不同的外部系統。所以六邊形架構是描述微服務架構中每個服務的架構的好方法。

根據我們具體的實踐經驗,比如在我們平時的項目中最常見的就是MySQL和Redis存儲,而且也很少改變為其他存儲結構。這里將分層架構和六邊形架構進行思想融合,目的是一方面希望我們的微服務設計結構更優美,另一方面希望在已有編程習慣的基礎上,更容易接受新的整潔架構思想。

我們項目中微服務的實現結合分層架構,六邊形架構和整潔架構的思想,以實際使用場景為背景,采用的應用程序結構圖如下。

從上圖可以看到,我們一個應用總共包含應用層application,領域層domain和基礎設施層infrastructure。領域服務的facade接口需要暴露給其他三方系統,所以單獨封裝為一個模塊。因為我們一般習慣於分層架構模式構建系統,所以按照分層架構給各層命名。

站在六邊形架構的角度,應用層application等同於入站適配器,基礎設施層infrastructure等同於出站適配器,所以實際上應用層和基礎設施層同屬外層,可以認為在同一層。

facade模塊其實是從領域層domain剝離出來的,站在整潔架構的角度,領域層就是內核業務實體,這里封裝的是整個業務領域中最通用、最高層的業務邏輯,一般情況下核心領域模型部分是比較穩定的,不受外界影響而變動。facade是微服務暴露給外界的領域服務能力,一般情況下接口的設定應符合當前領域服務的邊界界定,所以facade模塊屬於內核領域層。

facade接口的實現在應用層application的impl部分,符合整潔架構外層依賴內層的思想,對於impl輸入端口和入站適配器,可以采用不同的協議和技術框架實現,比如dubbo或HSF等。下面對各個模塊的構成進行逐一解釋。

2. 領域層Domain

工廠Factory

對象的創建本身是一個主要操作,但被創建的對象並不適合承擔復雜的裝配操作。將這些職責混在一起可能會產生難以理解的拙劣設計。讓客戶直接負責創建對象又會使客戶的設計陷入混亂,並且破壞裝配對象的封裝,而且導致客戶與被創建對象的實現之間產生過於緊密的耦合。

復雜對象的創建是領域層的職責,但這項任務並不屬於那些用於表示模型的對象。所以一般使用一個單獨的工廠類或者在領域服務中提供一個構造領域對象的接口來負責領域對象的創建。

這里,我們選擇給領域服務增加一個領域對象創建接口來承擔工廠的角色。

/**
 * description: 資源領域服務
 *
 * @author Gao Ju
 * @date 2020/7/27
 */
public class ResourceServiceImpl implements ResourceService {
 
    /**
     * 創建資源聚合模型
     *
     * @param resourceCreateCommand 創建資源命令
     * @return
     */
    @Override
    public ResourceModel createResourceModel(ResourceCreateCommand resourceCreateCommand) {
        ResourceModel resourceModel = new ResourceModel();
        Long resId = SequenceUtil.generateUuid();
        resourceModel.setResId(resId);
        resourceModel.setName(resourceCreateCommand .getName());
        resourceModel.setAuthor(resourceCreateCommand .getAuthor());
        List<PackageItem> packageItemList = new ArrayList<>();
        ...
        resourceModel.setPackageItemList(packageItemList);
        return resourceModel;
    }
}

資源庫Repository

通常將聚合實例存放在資源庫中,之后再通過該資源庫來獲取相同的實例。

如果修改了某個聚合,那么這種改變將被資源庫持久化,如果從資源庫中移除了某個實例,則將無法從資源庫中重新獲取該實例。

資源庫是針對聚合維度創建的,聚合類型與資源庫存在一對一的關系。

簡單來說,資源庫是對聚合的CRUD操作的封裝。資源庫內部采用哪種存儲設施MySQL,MongoDB或者Redis等,對領域層來說其實是不感知的。

資源repository構成圖

在我們的項目中采用MySQL作為資源repository的持久化存儲,上圖中每個DO對應一個數據庫表,當然你也可以采用其他存儲結構或設計為其他表結構,具體的處理流程均由repository進行封裝,對領域服務來說只感知Resource聚合維度的CRUD操作,示例代碼如下。

/**
 * description: 資源倉儲
 *
 * @author Gao Ju
 * @date 2020/08/23
 */
@Repository("resourceRepository")
public class ResourceRepositoryImpl implements ResourceRepository {
 
    /**
     * 資源Mapper
     */
    @Resource
    private ResourceMapper resourceMapper;
 
    /**
     * 資源包Mapper
     */
    @Resource
    private PackageMapper packageMapper;
 
    /**
     * 資源包預覽圖Mapper
     */
    @Resource
    private PackagePreviewMapper packagePreviewMapper;
 
    /**
     * 創建訂單信息
     *
     * @param resourceModel 資源聚合模型
     * @return
     */
    @Override
    public void add(ResourceModel resourceModel) {
        ResourceDO resourceDO = new ResourceDO();
        resourceDO.setName(resourceModel.getName());
        resourceDO.setAuthor(resourceModel.getAuthor());
        List<PackageDO> packageDOList = new ArrayList<>();
        List<PackagePreviewDO> packagePreviewDOList = new ArrayList<>();
        for (PackageItem packageItem : resourceModel.getPackageItemList()) {
            PackageDO packageDO = new PackageDO();
            packageDO.setResId(resourceModel.getResId());
            Long packageId = SequenceUtil.generateUuid();
            packageDO.setPackageId(packageId);
            for (PreviewItem previewItem: packageItem.getPreviewItemList()) {
                PackagePreviewDO packagePreviewDO = new PackagePreviewDO();
                ...
                packagePreviewDOList.add(packagePreviewDO);
            }
            packageDOList.add(packageDO);
        }
 
        resourceMapper.insert(resourceDO);
        packageMapper.insertBatch(packageDOList);
        packagePreviewMapper.insertBatch(packagePreviewDOList);
    }
}

你可能有疑問,按照整潔架構的思想,repository的接口定義在領域層,repository的實現應該定義在基礎設施層,這樣就符合外層依賴穩定度較高的內層了。

結合我們實際開發過程,一般存儲結構選定或者表結構設定后,一般不太容易做很大的調整,所以就按照習慣的分層結構使用,領域層直接依賴基礎設施層實現,降低編碼時帶來的額外習慣上的成本。

領域服務Service

領域驅動強調我們應該創建充血領域模型,將數據和行為封裝在一起,將領域模型與現實世界中的業務對象相映射。各類具備明確的職責划分,將領域邏輯分散到各個領域對象中。

領域中的服務表示一個無狀態的操作,它用於實現特定於某個領域的任務。當某個操作不適合放在領域對象上時,最好的方式是使用領域服務。

簡單總結領域服務本身所承載的職責,就是通過串聯領域對象、資源庫,生成並發布領域事件,執行事務控制等一系列領域內的對象的行為,為上層應用層提供交互的接口。

/**
 * description: 訂單領域服務
 *
 * @author Gao Ju
 * @date 2020/8/24
 */
public class UserOrderServiceImpl implements UserOrderService {
 
    /**
     * 訂單倉儲
     */
    @Autowired
    private OrderRepository orderRepository;
 
    /**
     * 消息發布器
     */
    @Autowired
    private MessagePublisher messagePublisher;
 
    /**
     * 訂單邏輯處理
     *
     * @param userOrder 用戶訂單
     */
    @Override
    public void createOrder(UserOrder userOrder) {
        orderRepository.add(userOrder);
        OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent();
        orderCreatedEvent.setUserId(userOrder.getUserId());
        orderCreatedEvent.setOrderId(userOrder.getOrderId());
        orderCreatedEvent.setPayPrice(userOrder.getPayPrice());
        messagePublisher.send(orderCreatedEvent);
    }
}

在實踐的過程中,為了簡單方便,我們仍然采用貧血領域模型,將領域對象自身行為和不屬於領域對象的行為都放在領域服務中實現。

大部分場景領域服務返回聚合根或者簡單類型,某些特殊場景也可以將聚合根中包含的實體或值對象返回給調用方。領域服務也可以同時操作多個領域對象,多個聚合,將其轉換為另外的輸出。

介於我們實際的使用場景,領域比較簡單,領域服務只操作一個領域的對象,只操作一個聚合,由應用服務來協調多個領域對象。

3. 領域事件DomainEvent

在領域驅動設計的上下文中,聚合在被創建時,或發生其他重大更改時發布領域事件,領域事件是聚合狀態更改時所觸發的。

領域事件命名時,一般選擇動詞的過去分詞,因為狀態改變時就代表當前事件已經發生,領域事件的每個屬性都是原始類型值或值對象,比如事件ID和創建時間等,事件ID也可以用來做冪等用。

從概念上講,領域事件由聚合負責發布,聚合知道其狀態何時發生變化,從而知道要發布的事件。

由於聚合不能使用依賴注入,需要通過方法參數的形式將消息發布器傳遞給聚合,但這將基礎設施和業務邏輯交織在一起,有悖於我們解耦設計的原則。

更好的方法是將事件發布放到領域服務中,因為服務可以使用依賴注入來獲取對消息發布器的引用,從而輕松發布事件。只要狀態發生變化,聚合就會生成事件,聚合方法的返回值中包括一個事件列表,並將它們返回給領域服務。

Saga是一種在微服務架構中維護數據一致性的機制,Sage由一連串的本地事務組成,每一個本地事務負責更新它所在服務的私有數據庫,通過異步消息的方式來協調一系列本地事務,從而維護多個服務之間數據的最終一致性。Saga包括協同式和編排式,

我們采用協同式來實現分布式事務,發布的領域事件以命令式消息的方式發送給Saga參與方。如果領域事件是自我發布自我消費,不依賴消息中間件實現,則可以使用事件總線模式來進行管理。下面以購買資源的過程為例進行說明。

購買資源的過程

  • 提交創建訂單請求,OrderService創建一個處於PAYING狀態的UserOrder,並發布OrderCreated事件。
  • UserService消費OrderCreated事件,驗證用戶是否可以下單,並發布UserVerified事件。
  • PaymentService消費UserVerified事件,進行實際的支付操作,並發布PaySuccess事件。
  • OrderService接收PaySuccess事件,將UserOrder狀態改為PAY_SUCCESS。

補償過程

  • PaymentService消費UserVerified事件,進行實際的支付操作,若支付失敗,並發布PayFailed事件。
  • OrderService接收PayFailed事件,將UserOrder狀態改為PAY_FAILED。

在Saga的概念中,

第1步叫可補償性事務,因為后面的步驟可能會失敗。

第3步叫關鍵性事務,因為它后面跟着不可能失敗的步驟。第4步叫可重復性事務,因為其總是會成功。

/**
 * description: 領域事件基類
 *
 * @author Gao Ju
 * @date 2020/7/27
 */
public class BaseEvent {
    /**
     * 消息唯一ID
     */
    private String messageId;
 
    /**
     * 事件類型
     */
    private Integer eventType;
 
    /**
     * 事件創建時間
     */
    private Date createTime;
 
    /**
     * 事件修改時間
     */
    private Date modifiedTime;
}
 
 
/**
 * description: 訂單創建事件
 *
 * @author Gao Ju
 * @date 2020/8/24
 */
public class OrderCreatedEvent extends BaseEvent {
 
    /**
     * 用戶ID
     */
    private String userId;
 
    /**
     * 訂單ID
     */
    private String orderId;
 
    /**
     * 支付價格
     */
    private Integer payPrice;
}

4.Facade模塊

facade和domain屬於同一層,某些提供給三方使用的類定義在facade,比如資源類型枚舉CategoryEnum限制三方資源使用范圍,然后domain依賴facade中enum定義。

另外,根據迪米特法則和告訴而非詢問原則,客戶端應該盡量少地知道服務對象內部結構,通過調用服務對象的公共接口的方式來告訴服務對象所要執行的操作。

所以,我們不應該把領域模型泄露到微服務之外,對外提供facade服務時,根據領域對象包裝出一個數據傳輸對象DTO(Data Transfer Object),來實現和外部三方系統的交互,比如上圖中的ResourceDTO。

5.應用層Application

應用層是業務邏輯的入口,由入站適配器調用。facade的實現,定時任務的執行和消息監聽處理器都屬於入站適配器,所以他們都位於應用層。

正常情況下一個微服務對應一個聚合,實踐過程中,某些場景下一個微服務可以包含多個聚合,應用層負責用例流的任務協調。領域服務依賴注入應用層,通過領域服務執行領域業務規則,應用層還會處理授權認證,緩存,DTO與領域對象之間的防腐層轉換等非領域操作。

/**
 * description: 訂單facade
 *
 * @author Gao Ju
 * @date 2020/8/24
 */
public class UserOrderFacadeImpl implements UserOrderFacade {
 
    /**
     * 訂單服務
     */
    @Resource
    private UserOrderService userOrderService;
 
    /**
     * 創建訂單信息
     *
     * @param orderPurchaseParam 訂單交易參數
     * @return
     */
    @Override
    public FacadeResponse<UserOrderPurchase> createOrder(OrderPurchaseParam orderPurchaseParam ) {
        UserOrder userOrder = new UserOrder();
        userOrder.setUserId(request.getUserId());
        userOrder.setResId(request.getResId());
        userOrder.setPayPrice(request.getPayAmount());
        userOrder.setOrderStatus(OrderStatusEnum.Create.getCode());
        userOrderService.handleOrder(userOrder);
        userOrderPurchase.setOrderId(userOrderDO.getId());
        userOrderPurchase.setCreateTime(new Date());
        return FacadeResponseFactory.getSuccessInstance(userOrderPurchase);
    }
}

6.基礎設施層 Infrastructure

基礎設施的職責是為應用程序的其他部分提供技術支持。與數據庫的交互dao模塊,與Redis緩存,本地緩存交互的cache模塊,與參數中心,三方rpc服務的交互,消息框架消息發布者都封裝在基礎設施層。

另外,程序中用到的工具類util模塊和異常類exception也統一封裝在基礎設施層。

從分層架構的角度,領域層可以依賴基礎設施層實現與其他外設的交互。另外,無論從分層架構的上層application層還是從六邊形架構的角度的輸入端口和適配器application,都可以依賴作為底層或處於同層的輸出端口和適配器的infrastructure層,比如調用util或者exception模塊。

四、結束語

其實,無論是面向服務架構SOA,微服務,領域驅動,還是中台,其目的都是在說,我們做架構設計的時候,應該從業務視角出發,對所涉及的業務領域,基於高內聚、低耦合的思想進行划分,最大限度且合理的實現業務重用。

這樣不僅方便提供專業且穩定的業務服務,更有利於業務的沉淀和可持續發展。業務之下是基於技術的系統實現,技術造就業務,業務引領技術,兩者相輔相成,共同為社會進步做出貢獻。

五、參考文獻

  • [1] 《領域驅動設計軟件核心復雜性應對之道》Eric Evans著, 趙俐 盛海燕 劉霞等譯,人民郵電出版社
  • [2] 《實現領域驅動設計》Vaughn Vernon著, 滕雲譯, 張逸審,電子工業出版社
  • [3] 《微服務架構設計模式》[美]克里斯.理查森(Chris Richardson) 著, 喻勇譯,機械工業出版社
  • [4] 《架構整潔之道》[美]Robert C.Martin 著,孫宇聰 譯,電子工業出版社
  • [5] 《企業IT架構轉型之道阿里巴巴中台戰略思想與架構實踐》鍾華編著,機械工業出版社
  • [6] 領域驅動設計(DDD)實踐之路(二):事件驅動與CQRS,vivo互聯網技術
  • [7] 領域驅動設計在互聯網業務開發中的實踐,美團技術團隊

作者:Angel Gao


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM