迄今爲止最完整的DDD實踐

一、爲什麼需要DDD

  • 複雜系統設計:系統多,業務邏輯複雜,概念不清晰,有什麼合適的方法幫助我們理清楚邊界,邏輯和概念?
  • 多團隊協同:邊界不清晰,系統依賴複雜,語言不統一導致溝通和理解困難。有沒有一種方式把業務和技術概念統一,大家用一種語言溝通。例如:航程是大家所理解的航程嗎?
  • 設計與實現一致性:PRD,詳細設計和代碼實現天差萬別。有什麼方法可以把業務需求快速轉換爲設計,同時還要保持設計與代碼的一致性?
  • 架構統一,可複用資產和擴展性:當前取決於開發的同學具備很好的抽象能力和高編程的技能。有什麼好的方法指導我們做抽象和實現。

二、DDD的價值

  • 邊界清晰的設計方法:通過領域劃分,識別哪些需求應該在哪些領域,不斷拉齊團隊對需求的認知,分而治之,控制規模。
  • 統一語言:團隊在有邊界的上下文中有意識地形成對事物進行統一的描述,形成統一的概念(模型)。
  • 業務領域的知識沉澱:通過反覆論證和提煉模型,使得模型必須與業務的真實世界保持一致。促使知識(模型)可以很好地傳遞和維護。
  • 面向業務建模:領域模型與數據模型分離,業務複雜度和技術複雜度分離。

三、DDD架構

3.1 分層架構

  • 用戶接口層:調用應用層完成具體用戶請求。包含:controller,遠程調用服務等
  • 應用層App:儘量簡單,不包含業務規則,而只爲了下一層中的領域對象做協調任務,分配工作,重點對領域層做編排完成複雜業務場景。包含:AppService,消息處理等
  • 領域層Domain:負責表達業務概念和業務邏輯,領域層是系統的核心。包含:模型,值對象,域服務,事件
  • 基礎層:對所有上層提供技術能力,包括:數據操作,發送消息,消費消息,緩存等
  • 調用關係:用戶接口層->應用層->領域層->基礎層
  • 依賴關係:用戶接口層->應用層->領域層->基礎層

3.2 六邊形架構

  • 六邊形架構:系統通過適配器的方式與外部交互,將應用服務於領域服務封裝在系統內部
  • 分層架構:它依然是分層架構,它核心改變的是依賴關係。
  • 領域層依賴倒置:領域層依賴基礎層倒置成基礎層依賴領域層,這個簡單的變化使得領域層不依賴任務層,其他層都依賴領域層,使得領域層只表達業務邏輯且穩定。

3.3 調用鏈路

四、DDD的基本概念

4.1 領域模型

  • 領域(戰略):業務範圍,範圍就是邊界。
  • 子領域:領域可大可小,我們將一個領域進行拆解形成子領域,子領域還可以進行拆解。當一個領域太大的時候需要進行細化拆解。
  • 模型(戰術):基於某個業務領域識別出這個業務領域的聚合,聚合根,界限上下文,實體,值對象。

4.1.1 核心域

決定產品和公司核心競爭力的子域是核心域,它是業務成功的主要因素和公司的核心競爭力。直接對業務產生價值。

4.1.2 通用域

沒有太多個性化的訴求,同時被多個子域使用的通用功能子域是通用域。例如,權限,登陸等等。間接對業務產生價值。

4.1.3 支撐域

支撐其他領域業務,具有企業特性,但不具有通用性。間接對業務產生價值。

4.1.4 爲什麼要劃分核心域、通用域和支撐域

一個業務一定有他最重要的部分,在日常做業務判斷和需求優先級判斷的時候可以基於這個劃分來做決策。例如:一個交易相關的需求和一個配置相關的需求排優先級,很明顯交易是核心域,規則是支持域。同樣我們認爲是支撐域或者通用域的在其他公司可能是核心域,例如權限對於我們來說是通用域,但是對於專業做權限系統的公司,這個是核心域。

4.2 限界上下文(戰略)

業務的邊界的劃分,這個邊界可以是一個領域或者多個領域的集合。複雜業務需要多個域編排完成一個複雜業務流程。限界上下文可以作爲微服務劃分的方法。其本質還是高內聚低耦合,只是限界上下文只是站在更高的層面來進行劃分。如何進行劃分,我的方法是一個界限上下文必須支持一個完整的業務流程,保證這個業務流程所涉及的領域都在一個限界上下文中。

4.3 實體(ENTITY)

定義:實體有唯一的標識,有生命週期且具有延續性。例如一個交易訂單,從創建訂單我們會給他一個訂單編號並且是唯一的這就是實體唯一標識。同時訂單實體會從創建,支付,發貨等過程最終走到終態這就是實體的生命週期。訂單實體在這個過程中屬性發生了變化,但訂單還是那個訂單,不會因爲屬性的變化而變化,這就是實體的延續性。

實體的業務形態:實體能夠反映業務的真實形態,實體是從用例提取出來的。領域模型中的實體是多個屬性、操作或行爲的載體。

實體的代碼形態:我們要保證實體代碼形態與業務形態的一致性。那麼實體的代碼應該也有屬性和行爲,也就是我們說的充血模型,但實際情況下我們使用的是貧血模型。貧血模型缺點是業務邏輯分散,更像數據庫模型,充血模型能夠反映業務,但過重依賴數據庫操作,而且複雜場景下需要編排領域服務,會導致事務過長,影響性能。所以我們使用充血模型,但行爲裏面只涉及業務邏輯的內存操作。

實體的運行形態:實體有唯一ID,當我們在流程中對實體屬性進行修改,但ID不會變,實體還是那個實體。

實體的數據庫形態:實體在映射數據庫模型時,一般是一對一,也有一對多的情況。

4.4 值對象(VALUEOBJECT)

定義:通過對象屬性值來識別的對象,它將多個相關屬性組合爲一個概念整體。在 DDD 中用來描述領域的特定方面,並且是一個沒有標識符的對象,叫作值對象。值對象沒有唯一標識,沒有生命週期,不可修改,當值對象發生改變時只能替換(例如String的實現)。

值對象的業務形態:值對象是描述實體的特徵,大多數情況一個實體有很多屬性,一般都是平鋪,這些數據進行分類和聚合後能夠表達一個業務含義,方便溝通而不關注細節。

值對象的代碼形態:實體的單一屬性是值對象,例如:字符串,整型,枚舉。多個屬性的集合也是值對象,這個時候我們把這個集合設計爲一個CLASS,但沒有ID。例如商品實體下的航段就是一個值對象。航段是描述商品的特徵,航段不需要ID,可以直接整體替換。商品爲什麼是一個實體,而不是描述訂單特徵,因爲需要表達誰買了什麼商品,所以我們需要知道哪一個商品,因此需要ID來標識唯一性。

我們看一下下面這段代碼,person這個實體有若干個單一屬性的值對象,比如Id、name等屬性;同時它也包含多個屬性的值對象,比如地址address。

值對象的運行形態:值對象創建後就不允許修改了,只能用另外一個值對象來整體替換。當我們修改地址時,從頁面傳入一個新的地址對象替換調用person對象的地址即可。如果我們把address設計成實體,必然存在ID,那麼我們需要從頁面傳入的地址對象的ID與person裏面的地址對像的ID進行比較,如果相同就更新,如果不同先刪除數據庫在新增數據。

值對象的數據庫形態:有兩種方式嵌入式和序列化大對象。

案例1:以屬性嵌入的方式形成的人員實體對象,地址值對象直接以屬性值嵌入人員實體中。

當我們只有一個地址的時候使用嵌入式比較好,如果多個地址必須有序列化大對象,同時可以支持搜索。

案例2:以序列化大對象的方式形成的人員實體對象,地址值對象被序列化成大對象Json串後,嵌入人員實體中。

支持多個地址存儲,不支持搜索。

值對象的優勢和侷限:

  1. 簡化數據庫設計,提升數據庫操作的性能(多表新增和修改,關聯表查詢)。
  2. 雖然簡化數據庫設計,但是領域模型還是可以表達業務。
  3. 序列化的方式會使搜索實現困難(通過搜索引擎可以解決)。

4.5 聚合和聚合根

多個實體和值對象組成的我們叫聚合,聚合的內部一定的高內聚。這個聚合裏面一定有一個實體是聚合根。

聚合與領域的關係:聚合也是範圍的劃分,領域也是範圍的劃分。領域與聚合可以是一對一,也可以是一對多的關係。

聚合根的作用是保證內部的實體的一致性,對外只需要對聚合根進行操作。

4.6 限界上下文,域,聚合,實體,值對象的關係

領域包含限界上下文,限界上下文包含子域,子域包含聚合,聚合包含實體和值對象。

4.7 事件風暴

參與者

除了領域專家,事件風暴的其他參與者可以是DDD專家、架構師、產品經理、項目經理、開發人員和測試人員等項目團隊成員。

事件風暴準備的材料

一面牆和一支筆。

事件風暴的關注點

在領域建模的過程中,我們需要重點關注這類業務的語言和行爲。比如某些業務動作或行爲(事件)是否會觸發下一個業務動作,這個動作(事件)的輸入和輸出是什麼?是誰(實體)發出的什麼動作(命令),觸發了這個動作(事件)…我們可以從這些暗藏的詞彙中,分析出領域模型中的事件、命令和實體等領域對象。

實體執行命令產生事件。

業務場景的分析

通過業務場景和用例找出實體,命令,事件。

領域建模

領域建模時,我們會根據場景分析過程中產生的領域對象,比如命令、事件等之間關係,找出產生命令的實體,分析實體之間的依賴關係組成聚合,爲聚合劃定限界上下文,建立領域模型以及模型之間的依賴。領域模型利用限界上下文向上可以指導微服務設計,通過聚合向下可以指導聚合根、實體和值對象的設計。

五、如何建模

  • 用例場景梳理:就是一句話需求,但我們需要把一些模糊的概念通過對話的方式逐步得到明確的需求,在加以提煉和抽象。
  • 建模方法論:詞法分析(找名詞和動詞),領域邊界
  • 模型驗證

5.1 協同單自動化分單案例

5.1.1 領域建模

需求:我們需要把系統自動化失敗轉人工訂單自動分配給小二,避免人工挑單和搶單,通過自動分配提升整體履約處理效率。

  • 產品小A:把需求讀了一遍.......。
  • 開發小B:那就是將履約單分配給個小二對吧?
  • 產品小A:不對,我們還需要根據一個規則自動分單,例如退票訂單分給退票的小二
  • 開發小B:恩,那我們可以做一個分單規則管理。例如:新增一個退票分單規則,在裏面添加一批小二工號。履約單基於自身屬性去匹配分單規則並找到一個規則,然後從分單規則裏面選擇一個小二工號,履約單寫入小二工號即可。

  • 產品小A:分單規則還需要有優先級,其中小二如果上班了才分配,如果下班了就不分配。
  • 開發小B:優先級沒有問題,在匹配分單規則方法裏面按照優先級排序即可,不影響模型。而小二就不是簡單一個工號維護在分單規則中,小二有狀態了。

  • 產品小A:分單規則裏面添加小二操作太麻煩了,例如:每次新增一個規則都要去挑人,人也不一定記得住,實際客服在管理小二的時候是按照技能組管理的。
  • 開發小B:恩,懂了,那就是通過新增一個技能組管理模塊來管理小二。然後在通過分單規則來配置1個技能組即可。獲取一個小二工號就在技能組裏面了。

  • 開發小B:總感覺不對,因爲新增一個自動化分單需求,履約單就依賴了分單規則,履約單應該是一個獨立的域,分單不是履約的能力,履約單實際只需要知道處理人是誰,至於怎麼分配的他不太關心。應該由分單規則基於履約單屬性找匹配一個規則,然後基於這個規則找到一個小二。履約單與分單邏輯解耦。

  • 產品小A:分單要輪流分配或者能者多勞分配,小二之前處理過的訂單和航司優先分配。
  • 開發小B:獲取小二的邏輯越來越複雜了,實際技能組纔是找小二的核心,分單規則核心是通過履約單特徵得到一個規則結果(技能組ID,分單策略,特徵規則)。技能組基於分單規則的結果獲得小二工號。

  • 產品小A:還漏了一個信息,就是履約單會被多次分配的情況,每一個履約環節都可能轉人工,客服需要知道履約單被處理多次的情況
  • 開發小B:那用履約單無法表達了,我們需要新增一個概念叫協同單,協同單是爲了協同履約單,通過協同推進履約單的進度。

  • 產品小A:協同單概念很好,小二下班後,如果沒有處理完,還可以轉交給別人。
  • 開發小B:恩,那隻需要在協同單上增加行爲即可。

5.1.2 領域劃分

溝通的過程就是推導和驗證模型的過程,最後進行域的劃分:

5.1.3 場景梳理

窮舉所有場景,重新驗證模型是否可以覆蓋所有場景。

場景名稱 場景動作 域服務 聚合根 方法
創建協同單 1、判斷關聯業務單是否非法 協同單 創建協同單1、問題分類是否符合條件(例如:商家用戶發起自營->商家的協同單)2、save 協同單 創建協同單
分配協同單 協同單ID 分配協同單到人.1、判斷協同單狀態(=待處理)2、記錄操作日誌3、save 協同單 分配協同單 協同單 分配協同單
受理協同單 協同單ID 處理協同單 協同單 受理協同單1.判斷訂單狀態(=待處理/驗收失敗)2.更改訂單狀態(待處理/驗收失敗->處理中)3.記錄操作日誌4.save 協同單 受理協同單
轉交協同單 協同單ID 轉交協同單 協同單 轉交協同單1.判斷訂單狀態.(=處理中、待處理)2.校驗轉交的人是否在正確的組織下面3.更改協同人值對象(同一組織下的不同人,從坐席管理域中取)4.記錄操作日誌5.save 協同單 轉交協同單
關閉協同單 協同單ID 關閉協同單 協同單 關閉協同單1.判斷訂單狀態(=處理中、待處理)2.更改訂單狀態(關閉)3.記錄操作日誌4.save 協同單 關閉協同單
處理協同單 協同單ID 處理協同單 協同單 處理協同單1.判斷訂單狀態(=處理中)2.更改訂單狀態(處理中->待驗收)3.記錄操作日誌4.save 協同單 處理協同單
駁回協同單 協同單ID 駁回協同單 協同單 駁回協同單1.判斷訂單狀態(=待驗收)2.更改訂單狀態(待驗收->處理中)3.記錄操作日誌4.save 協同單 駁回協同單
完結協同單 協同單ID 完結協同單 協同單 完結協同單1.判斷訂單狀態(=待驗收)2.更改訂單狀態(待驗收->已完結)3.記錄操作日誌4.save 協同單 完結協同單
拒絕協同單 協同單ID 拒絕協同單 協同單 拒絕協同單1.判斷訂單狀態(=處理中、待處理)2.更改訂單狀態(已拒絕)3.記錄操作日誌4.save 協同單 拒絕協同單
催單 協同單ID 催單 協同單 催單1.判斷訂單狀態(=處理中、待處理)2、修改催單值對象3、記錄操作日誌4、save 協同單 催單

六、怎麼寫代碼

6.1 DDD規範

每一層都定義了相應的接口主要目的是規範代碼:

  • application:CRQS模式,ApplicationCmdService是command,ApplicationQueryService是query
  • service:是領域服務規範,其中定義了DomainService,應用系統需要繼承它。
  • model:是聚合根,實體,值對象的規範。
    • Aggregate和BaseAggregate:聚合根定義
    • Entity和BaseEntity:實體定義
    • Value和BaseValue:值對象定義
    • Param和BaseParam:領域層參數定義,用作域服務,聚合根和實體的方法參數
    • Lazy:描述聚合根屬性是延遲加載屬性,類似與hibernate。
    • Field:實體屬性,用來實現update-tracing
/**
 * 實體屬性,update-tracing
 * @param <T>
 */
public final class Field<T> implements Changeable {
    private boolean changed = false;
    private T value;
    private Field(T value){
        this.value = value;
    }
    public void setValue(T value){
        if(!equalsValue(value)){
            this.changed = true;
        }
        this.value = value;
    }
    @Override
    public boolean isChanged() {
        return changed;
    }
    public T getValue() {
        return value;
    }
    public boolean equalsValue(T value){
        if(this.value == null && value == null){
            return true;
        }
        if(this.value == null){
            return false;
        }
        if(value == null){
            return false;
        }
        return this.value.equals(value);
    }
    public static <T> Field<T> build(T value){
        return new Field<T>(value);
    }
}
  • repository
    • Repository:倉庫定義
    • AggregateRepository:聚合根倉庫,定義聚合根常用的存儲和查詢方法
  • event:事件處理
  • exception:定義了不同層用的異常
    • AggregateException:聚合根裏面拋的異常
    • RepositoryException:基礎層拋的異常
    • EventProcessException:事件處理拋的

6.2 工程結構

6.2.1 application模塊

  • CRQS模式:commad和query分離。
  • 重點做跨域的編排工作,無業務邏輯。

6.2.2 domain模塊

  • 域服務,聚合根,值對象,領域參數,倉庫定義

6.2.3 infrastructurre模塊

所有技術代碼在這一層。mybatis,redis,mq,job,opensearch代碼都在這裏實現,domain通過依賴倒置不依賴這些技術代碼和JAR。

6.2.4 client模塊

對外提供服務

6.2.5 model模塊

內外都要用的共享對象

6.3 代碼示例

6.3.1 application示例

public interface CaseAppFacade extends ApplicationCmdService {
    /**
     * 接手協同單
     * @param handleCaseDto
     * @return
     */
    ResultDO<Void> handle(HandleCaseDto handleCaseDto);

}
public class CaseAppImpl implements CaseAppFacade {
    @Resource
    private CaseService caseService;//域服務
    @Resource
    CaseAssembler caseAssembler;//DTO轉Param
    @Override
    public ResultDO<Void> handle(HandleCaseDto handleCaseDto) {
        try {
            ResultDO<Void> resultDO = caseService.handle(caseAssembler.from(handleCaseDto));
            if (resultDO.isSuccess()) {
                pushMsg(handleCaseDto.getId());
                return ResultDO.buildSuccessResult(null);
            }
            return ResultDO.buildFailResult(resultDO.getMsg());
        } catch (Exception e) {
            return ResultDO.buildFailResult(e.getMessage());
        }
    }
}
  • mapstruct:VO,DTO,PARAM,DO,PO轉換非常方便,代碼量大大減少。
  • CaseAppImpl.handle調用域服務caseService.handle。

6.3.2 domainService示例

public interface CaseService extends DomainService {
    /**
     * 接手協同單
     *
     * @param handleParam
     * @return
     */
    ResultDO<Void> handle(HandleParam handleParam);
    
}
public class CaseServiceImpl implements CaseService {
    @Resource
  private CoordinationRepository coordinationRepository;

    @Override
    public ResultDO<Void> handle(HandleParam handleParam) {
        SyncLock lock = null;
        try {
            lock = coordinationRepository.syncLock(handleParam.getId().toString());
            if (null == lock) {
                return ResultDO.buildFailResult("協同單handle加鎖失敗");
            }
            CaseAggregate caseAggregate = coordinationRepository.query(handleParam.getId());
            caseAggregate.handle(handleParam.getFollowerValue());
            coordinationRepository.save(caseAggregate);
            return ResultDO.buildSuccessResult(null);
        } catch (RepositoryException | AggregateException e) {
            String msg = LOG.error4Tracer(OpLogConstant.traceId(handleParam.getId()), e, "協同單handle異常");
            return ResultDO.buildFailResult(msg);
        } finally {
            if (null != lock) {
                coordinationRepository.unlock(lock);
            }
        }
    }
}
  • 領域層不依賴基礎層的實現:coordinationRepository只是接口,在領域層定義好,由基礎層依賴領域層實現這個接口。
  • 業務邏輯和技術解耦:域服務這層通過調用coordinationRepository和聚合根將業務邏輯和技術解耦。
  • 聚合根的方法無副作用:聚合根的方法只對聚合根內部實體屬性的改變,不做持久化動作,可反覆測試。
  • 模型與數據分離:
    • 改變模型:caseAggregate.handle(handleParam.getFollowerValue())。
    • 改變數據:coordinationRepository.save(caseAggregate);事務是在save方法上。

6.3.3 Aggregate,Entity示例

public class CaseAggregate extends BaseAggregate implements NoticeMsgBuilder {
    private final CaseEntity caseEntity;
    public CaseAggregate(CaseEntity caseEntity) {
        this.caseEntity = caseEntity;
    }
    /**
     * 接手協同單
     * @param followerValue
     * @return
     */
    public void handle(FollowerValue followerValue) throws AggregateException {
        try {
            this.caseEntity.handle(followerValue);
        } catch (Exception e) {
            throw e;
        }
    }
}
public class CaseEntity extends BaseEntity {
    /**
     * 創建時間
     */
    private Field<Date> gmtCreate;
    /**
     * 修改時間
     */
    private Field<Date> gmtModified;
    /**
     * 問題分類
     */
    private Field<Long> caseType;
    /**
     * 是否需要支付
     */
    private Field<Boolean> needPayFlag;
    /**
     * 是否需要自動驗收通過協同單
     */
    private Field<Integer> autoAcceptCoordinationFlag;
    /**
     * 發起協同人值對象
     */
    private Field<CreatorValue> creatorValue;
    /**
     * 跟進人
     */
    private Field<FollowerValue> followerValue;
    /**
     * 狀態
     */
    private Field<CaseStatusEnum> status;
    /**
     * 關聯協同單id
     */
    private Field<String> relatedCaseId;
    /**
     * 關聯協同單類型
     * @see 讀配置 com.alitrip.agent.business.flight.common.model.dataobject.CoordinationCaseTypeDO
     */
    private Field<String> relatedBizType;

    /**
     * 支付狀態
     */
    private Field<PayStatusEnum> payStatus;
    省略....

    public CaseFeatureValue getCaseFeatureValue() {
        return get(caseFeatureValue);
    }
    public Boolean isCaseFeatureValueChanged() {
        return caseFeatureValue.isChanged();
    }

    public void setCaseFeatureValue(CaseFeatureValue caseFeatureValue) {
        this.caseFeatureValue = set(this.caseFeatureValue, caseFeatureValue);
    }

    public Boolean isPayStatusChanged() {
        return payStatus.isChanged();
    }

    public Boolean isGmtCreateChanged() {
        return gmtCreate.isChanged();
    }

    public Boolean isGmtModifiedChanged() {
        return gmtModified.isChanged();
    }

    public Boolean isCaseTypeChanged() {
        return caseType.isChanged();
    }
    省略....

   
    /**
    * 接手
    */
    public void handle(FollowerValue followerValue) throws AggregateException {
        if (isWaitProcess()||isAppointProcess()) {
            this.setFollowerValue(followerValue);
            this.setStatus(CaseStatusEnum.PROCESSING);
            this.setGmtModified(new Date());
            initCaseRecordValue(CaseActionNameEnum.HANDLE, null, followerValue);
        } else {
            throwStatusAggregateException();
        }
    }
    省略....
}
  • 充血模型VS貧血模型:
    • 充血模型:表達能力強,代碼高內聚,領域內封閉,聚合根內部結構對外不可見,通過聚合根的方法訪問,適合複雜企業業務邏輯。
    • 貧血模型:業務複雜之後,邏輯散落到大量方法中。
  • 規範大於技巧:DDD架構可以避免引入一些其他概念,系統只有域,域服務,聚合根,實體,值對象,事件來構建系統。
 

聚合根的reconProcess的方法的業務邏輯被reconHandler和reconRiskHandler處理,必然這些handler要訪問聚合根裏面的實體的屬性,那麼邏輯就會散落。修改後:

沒有引入其他概念,都是在聚合根裏面組織實體完成具體業務邏輯,去掉了handler這種技術語言。

  • 聚合根和實體定義的方法是具備單一原則,複用性原則與使用場景無關,例如:不能定義手工創建協調單和系統自動創建協同單,應該定義創建協同單。
  • Update-tracing:handle方法修改屬性後,然後調用 coordinationRepository.save(caseAggregate),我們只能全量屬性更新。Update-tracing是監控實體的變更。Entiy定義屬性通過Field進行包裝實現屬性的變更狀態記錄,結合mapstruct轉換PO實現Update-tracing。

修改了mapstruct生成轉換代碼的源碼,修改後生成的代碼:

if(caseEntity.isAppended() || caseEntity.isCaseTypeChanged()){
    casePO.setCaseType( caseEntity.getCaseType() );
}

當屬性被改變後就轉換到po中,這樣就可以實現修改後的字段更新。

  • idea的get和set方法自動生成:由於使用field包裝,需要自定義get和set生成代碼。

6.3.4 Repository示例

public interface CoordinationRepository extends Repository {  
  /**
     * 保存/更新
     * @param aggregate
     * @throws RepositoryException
     */
   void save(CaseAggregate aggregate) throws RepositoryException;
}
@Repository
public class CoordinationRepositoryImpl implements CoordinationRepository {
  @Override
    public void save(CaseAggregate aggregate) throws RepositoryException {
        try {
            
            //聚合根轉PO,update-tracing技術
            CasePO casePO = caseConverter.toCasePO(aggregate.getCase());
            CasePO oldCasePO = null;
            if (aggregate.getCase().isAppended()) {
                casePOMapper.insert(casePO);
                aggregate.getCase().setId(casePO.getId());
            } else {
                oldCasePO = casePOMapper.selectByPrimaryKey(casePO.getId());
                casePOMapper.updateByPrimaryKeySelective(casePO);
            }
            // 發送協同單狀態改變消息
            if (CaseStatusEnum.FINISH.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.WAIT_DISTRIBUTION.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.PROCESSING.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.APPOINT_PROCESS.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.WAIT_PROCESS.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.CLOSE.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.REJECT.getCode().equals(casePO.getStatus())
                || CaseStatusEnum.PENDING_ACCEPTANCE.getCode().equals(casePO.getStatus())) {

                FollowerDto followerDto = new FollowerDto();
                followerDto.setCurrentFollowerId(aggregate.getCase().getFollowerValue().getCurrentFollowerId());
                followerDto.setCurrentFollowerGroupId(aggregate.getCase().getFollowerValue().getCurrentFollowerGroupId());
                followerDto.setCurrentFollowerType(aggregate.getCase().getFollowerValue().getCurrentFollowerType());
                followerDto.setCurrentFollowerName(aggregate.getCase().getFollowerValue().getCurrentFollowerName());
                //拒絕和關閉都使用CLOSE
                String tag = CaseStatusEnum.codeOf(casePO.getStatus()).name();
                if(CaseStatusEnum.REJECT.name().equals(tag)){
                    tag = CaseStatusEnum.CLOSE.name();
                }
                statusChangeProducer.send(CaseStatusChangeEvent.build()
                    .setId(casePO.getId())
                    .setFollowerDto(followerDto)
                    .setStatus(aggregate.getCase().getStatus().getCode())
                    .setCaseType(aggregate.getCase().getCaseType())
                    .setOldStatus(null != oldCasePO ? oldCasePO.getStatus() : null)
                    .setAppointTime(aggregate.getCase().getAppointTime()), (tag));
            }

            // 操作日誌
            if (CollectionUtils.isNotEmpty(aggregate.getCase().getCaseRecordValue())) {
                CaseRecordValue caseRecordValue = Lists.newArrayList(aggregate.getCase().getCaseRecordValue()).get(0);
                caseRecordValue.setCaseId(casePO.getId());
                recordPOMapper.insert(caseConverter.from(caseRecordValue));
            }

        } catch (Exception e) {
            throw new RepositoryException("", e.getMessage(), e);
        }
    }
}
  • CoordinationRepository接口定義在領域層。
  • CoordinationRepositoryImpl實現在基礎層:數據庫操作都是基於聚合根操作,保證聚合根裏面的實體強一致性。

最後結束語

  • 好的模型,可以沉澱組織資產,不好的模型,逐漸成爲負債。
  • 功能纔是表象,模型纔是內在。
  • 建模過程是不斷猜想與反駁的過程。
  • 演化觀點是建模過程的基本心智模式。

作者|章磊

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章