1. 程式人生 > 其它 >領域驅動設計(2) 領域事件、DDD分層架構

領域驅動設計(2) 領域事件、DDD分層架構

領域事件

什麼是領域事件

在事件風暴過程中,會識別出命令、業務操作、實體等,此外還有事件。比如當業務人員的描述中出現類似“當完成...後,則...”,“當發生...時,則...”等模式時,往往可將其用領域事件來實現。
領域事件表示在領域中發生的事件,它會導致進一步的業務操作。如電商中,支付完成後觸發的事件,會導致生成訂單、扣減庫存等操作。

為什麼需要領域事件

領域事件的最終目的是為了實現聚合之間的解耦。事件模式是一種通用的解耦方法,相比依賴間的直接呼叫,通過事件方式形成的間接依賴,在擴充套件、重構時更加靈活。
在上一篇領域驅動設計(1) DDD的一些基礎概念

中提到設計聚合的原則之一:

在邊界之外使用最終一致性。聚合內資料強一致性,聚合之間資料最終一致性。在一次事務中,最多隻能更改一個聚合的狀態。如何一個業務操作涉及多個聚合狀態的更改,可以採用領域事件的方式,實現聚合之間的解耦;

聚合根管理多個實體,可以在單個程序內實現資料的強一致性;將DDD實現為微服務時,聚合間的互動可能需要採用程序間的通訊的方式(HTTP,RPC,MQ),此時如果仍然採用強一致性代價太高,且可靠性差,而最終一致性就成了更好的選擇。(領域)事件就是實現最終一致性的一種方式。

領域事件可以切斷領域模型之間的強依賴關係,釋出方釋出事件後不需要關注訂閱方處理事件是否成功,這樣實現領域模型的解耦,保證領域模型間的獨立性,同時也能實現資料的最終一致性。

微服務場景下的領域事件

領域事件發生在聚合之間,聚合也是微服務拆分的最小單元。但實踐中並不一定每個聚合都拆分為獨立的微服務,可能多個關聯性高的聚合會被置於同一個微服務中,所以用於聚合間互動的領域事件,在微服務場景下可以細分為微服務內和微服務間的領域事件。

微服務內的領域事件

微服務內的領域事件不是必須的,因為微服務內的操作都發生在同一個程序,可以較方便地控制事務。
假設微服務包含多個聚合,不管是聚合內,還是這些聚合間的互動實際上都發生在程序內,可以做到強一致性。但這不符合DDD在一次事務中,最多隻能更改一個聚合的狀態的原則,此外聚合間採用同步呼叫的方式也會帶來強耦合。
所以可以考慮採用事件匯流排的實現方式,但事件驅動本身的複雜度大於同步呼叫,在微服務內處理聚合間的互動還有另一種選擇,那就是在應用層來編排和組合跨聚合的呼叫。
可以結合具體場景分析各種方式的成本和收益。

微服務間的領域事件

微服務間的領域事件用於在跨聚合甚至跨限界上下文間實現業務協作,其主要目的是實現微服務解耦,用非同步的領域事件代替同步呼叫,可以避免微服務間的彈性依賴。

領域事件的實現

領域事件設計事件的生成和釋出、事件持久化、事件匯流排、訊息中介軟體、事件訂閱和處理等。

  • 事件的生成和釋出:構建的事件應包含事件ID、時間戳、事件型別、事件源等基本屬性,以便事件可以無歧義地在不同上下文間傳播;此外事件還應包含具體的業務資料。
  • 事件持久化:分散式系統需要考慮分割槽容忍性,而事件持久化可以保證在發生分割槽時系統仍然可用,等分割槽恢復後,系統可繼續處理已經持久化的事件。另一方面,持久化的事件還可以用於系統之間的資料對帳或審計。事件持久化的實現,可以選擇儲存在當前服務所屬的資料庫,這可以利用本地事務保證資料的強一致性;也可以選擇儲存在程序外的事件資料庫中,但這需要處理分散式事務。
  • 事件匯流排:用於實現微服務內的事件驅動模式,如mediatR;
  • 訊息中介軟體:用於跨微服務的事件傳遞,常用的有kafaka、rabbitMQ等;
  • 事件訂閱和處理:訂閱事件的一方在處理事件前,視業務場景需要,可以選擇先儲存事件再處理,或者直接處理。

DDD的分層架構

實現DDD的架構有很多種,比如整潔架構(洋蔥架構)、六邊形架構、DDD分層架構等等。對整潔架構(洋蔥架構)、六邊形架構不做了解,只focus在DDD分層架構。

  • 使用者介面層:暴露使用者介面、web服務之類的介面,這裡使用者還包括其它依賴的服務、自動化測試、批處理指令碼等;
  • 應用層:應用層包含應用服務,這一層應當很薄,在應用服務進行聚合間的編排、組合;如果需要呼叫程序外別的服務,也可以在應用層進行;許可權校驗、事務控制也可以放在這一層;
  • 領域層:這一層包含核心的業務邏輯,且應該儘量保持穩定,不依賴別的層。對基礎層的依賴通過依賴倒置的方式解決;
  • 基礎設施層:提供基礎設施如資料庫、事件匯流排、API閘道器、快取、檔案系統等等;作為領域層介面的能力供應商。

DDD分層架構如何推動微服務演進

領域模型中物件的層次從下到上依次有值物件、實體、聚合、限界上下文。
其中值物件和實體的功能變化,會影響微服務內部的實現變化;聚合的功能變化、重組則會影響微服務的拆分、合併。
首先,在微服務內部,實體的方法被領域服務組合和封裝,而領域服務又被應用服務組合和封裝。領域層通常只提供一些原子的方法,這些方法在應用層被組合起來提供完整的功能,比如原先提供了領域服務A、領域服務B、領域服務C,應用層按需組合這些領域服務,但過一段時間後發現領域服務B、領域服務C總是被同時呼叫,那麼就可以將他倆重構為一個領域服務。
然後,在微服務之間,可能會用限界上下文來分割微服務,但實際上聚合才是微服務劃分的基本單元,因為聚合是最小的、業務內聚的單元,聚合可以獨立完成特定的業務邏輯。所以有的時候會將同一限界上下文的聚合拆分為不同的微服務。假設原先微服務中包含聚合A、聚合B,但實際執行中發現聚合A的訪問頻次遠高於聚合B,導致聚合B的效能受到了影響。此時就可以將聚合A獨立為單獨的微服務。

PS:徐昊在極客時間《如何落地業務建模》中認為,劃分微服務的依據應該是彈性邊界。從此處DDD對微服務的處理來看,徐昊老師的觀點可謂一針見血。比如這裡的聚合A與聚合B拆開的例子,明顯兩者處於不同的彈性邊界。

參考資料: 歐創新 《DDD實戰課》