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

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

1、單體架構的弊端

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

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

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

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

一、複雜性高

在項目初期應該有人能夠作到對應用各個功能和實現瞭如指掌,隨着業務需求的增多,各類業務流程錯綜複雜的揉在一塊兒,整個系統變得龐大且複雜,以致於不多有開發者清楚每個功能和業務流程細節。安全

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

2.技術債務多

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

3.錯誤難隔離

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

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

多個相似的業務項目之間勢必會存在相似的功能模塊,若是都採用單體模式,就會帶來重複功能建設和維護。並且,有時候還須要互相產生交互,打通單體系統之間的交互集成和協做的成本也須要額外付出。併發

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

5.擴展成本高

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

針對以上痛點,近年來愈來愈多的互聯網公司採用「微服務」架構構建自身的業務平臺,而「微服務」也得到了愈來愈多技術人員的確定。

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

2、微服務設計

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級別的大應用層使用場景,理論當然是理論,仍是須要結合實際狀況靈活應用。

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

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模塊。

4、結束語

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

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

5、參考文獻

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