DDD術語-領域事件(Domain Event)


領域事件是領域模型中非常重要的一部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,在實現業務解耦的同時,還有助於形成完整的業務閉環。

領域事件可以是業務流程的一個步驟;也可能是定時批處理過程中發生的事件;或者一個事件發生后觸發的后續動作,比如密碼連續輸錯三次,觸發鎖定賬戶的動作。

那如何識別領域事件呢?很簡單,和剛才講的定義是強關聯的。在做用戶旅程或者場景分析時,我們要捕捉業務、需求人員或領域專家口中的關鍵詞:“如果發生……,則……”“當做完……的時候,請通知……”“發生……時,則……”等。在這些場景中,如果發生某種事件后,會觸發進一步的操作,那么這個事件很可能就是領域事件。

那領域事件為什么要用最終一致性,而不是傳統 SOA 的直接調用的方式呢?

聚合的一個設計原則:在邊界之外使用最終一致性。一次事務最多只能更改一個聚合的狀態。如果一次業務操作涉及多個聚合狀態的更改,應采用領域事件的最終一致性。

領域事件驅動設計可以切斷領域模型之間的強依賴關系,事件發布完成后,發布方不必關心后續訂閱方事件處理是否成功,這樣可以實現領域模型的解耦,維護領域模型的獨立性和數據的一致性。在領域模型映射到微服務系統架構時,領域事件可以解耦微服務,微服務之間的數據不必要求強一致性,而是基於事件的最終一致性。

回到具體的業務場景,我們發現有的領域事件發生在微服務內的聚合之間,有的則發生在微服務之間,還有兩者皆有的場景,一般來說跨微服務的領域事件處理居多。在微服務設計時不同領域事件的處理方式會不一樣。

1. 微服務內的領域事件

當領域事件發生在微服務內的聚合之間,領域事件發生后完成事件實體構建和事件數據持久化,發布方聚合將事件發布到事件總線,訂閱方接收事件數據完成后續業務操作。

微服務內大部分事件的集成,都發生在同一個進程內,進程自身可以很好地控制事務,因此不一定需要引入消息中間件。但一個事件如果同時更新多個聚合,按照 DDD“一次事務只更新一個聚合”的原則,你就要考慮是否引入事件總線。但微服務內的事件總線,可能會增加開發的復雜度,因此你需要結合應用復雜度和收益進行綜合考慮。

微服務內應用服務,可以通過跨聚合的服務編排和組合,以服務調用的方式完成跨聚合的訪問,這種方式通常應用於實時性和數據一致性要求高的場景。這個過程會用到分布式事務,以保證發布方和訂閱方的數據同時更新成功。

2. 微服務之間的領域事件

跨微服務的領域事件會在不同的限界上下文或領域模型之間實現業務協作,其主要目的是實現微服務解耦,減輕微服務之間實時服務訪問的壓力。

領域事件發生在微服務之間的場景比較多,事件處理的機制也更加復雜。跨微服務的事件可以推動業務流程或者數據在不同的子域或微服務間直接流轉。

跨微服務的事件機制要總體考慮事件構建、發布和訂閱、事件數據持久化、消息中間件,甚至事件數據持久化時還可能需要考慮引入分布式事務機制等。

微服務之間的訪問也可以采用應用服務直接調用的方式,實現數據和服務的實時訪問,弊端就是跨微服務的數據同時變更需要引入分布式事務,以確保數據的一致性。分布式事務機制會影響系統性能,增加微服務之間的耦合,所以我們還是要盡量避免使用分布式事務。

總之,通過領域事件驅動的異步化機制,可以推動業務流程和數據在各個不同微服務之間的流轉,實現微服務的解耦,減輕微服務之間服務調用的壓力,提升用戶體驗。

領域事件總體架構

領域事件的執行需要一系列的組件和技術來支撐。我們來看一下這個領域事件總體技術架構圖,領域事件處理包括:事件構建和發布、事件數據持久化、事件總線、消息中間件、事件接收和處理等。下面我們逐一講一下。

 

 

 

1. 事件構建和發布

事件基本屬性至少包括:事件唯一標識、發生時間、事件類型和事件源,其中事件唯一標識應該是全局唯一的,以便事件能夠無歧義地在多個限界上下文中傳遞。事件基本屬性主要記錄事件自身以及事件發生背景的數據。

另外事件中還有一項更重要,那就是業務屬性,用於記錄事件發生那一刻的業務數據,這些數據會隨事件傳輸到訂閱方,以開展下一步的業務操作。

事件基本屬性和業務屬性一起構成事件實體,事件實體依賴聚合根。領域事件發生后,事件中的業務數據不再修改,因此業務數據可以以序列化值對象的形式保存,這種存儲格式在消息中間件中也比較容易解析和獲取。

為了保證事件結構的統一,我們還會創建事件基類 DomainEvent(參考下圖),子類可以擴充屬性和方法。由於事件沒有太多的業務行為,實現方法一般比較簡單。

事件發布之前需要先構建事件實體並持久化。事件發布的方式有很多種,你可以通過應用服務或者領域服務發布到事件總線或者消息中間件,也可以從事件表中利用定時程序或數據庫日志捕獲技術獲取增量事件數據,發布到消息中間件。

2. 事件數據持久化

事件數據持久化可用於系統之間的數據對賬,或者實現發布方和訂閱方事件數據的審計。當遇到消息中間件、訂閱方系統宕機或者網絡中斷,在問題解決后仍可繼續后續業務流轉,保證數據的一致性。

事件數據持久化有兩種方案,在實施過程中你可以根據自己的業務場景進行選擇。

持久化到本地業務數據庫的事件表中,利用本地事務保證業務和事件數據的一致性。

持久化到共享的事件數據庫中。這里需要注意的是:業務數據庫和事件數據庫不在一個數據庫中,它們的數據持久化操作會跨數據庫,因此需要分布式事務機制來保證業務和事件數據的強一致性,結果就是會對系統性能造成一定的影響。

3. 事件總線 (EventBus)

事件總線是實現微服務內聚合之間領域事件的重要組件,它提供事件分發和接收等服務。事件總線是進程內模型,它會在微服務內聚合之間遍歷訂閱者列表,采取同步或異步的模式傳遞數據。事件分發流程大致如下:

如果是微服務內的訂閱者(其它聚合),則直接分發到指定訂閱者;

如果是微服務外的訂閱者,將事件數據保存到事件庫(表)並異步發送到消息中間件;

如果同時存在微服務內和外訂閱者,則先分發到內部訂閱者,將事件消息保存到事件庫(表),再異步發送到消息中間件。

4. 消息中間件

跨微服務的領域事件大多會用到消息中間件,實現跨微服務的事件發布和訂閱。消息中間件的產品非常成熟,市場上可選的技術也非常多,比如 Kafka,RabbitMQ 等。

5. 事件接收和處理

微服務訂閱方在應用層采用監聽機制,接收消息隊列中的事件數據,完成事件數據的持久化后,就可以開始進一步的業務處理。領域事件處理可在領域服務中實現。

領域事件運行機制相關案例

這里我用承保業務流程的繳費通知單事件,來給你解釋一下領域事件的運行機制。這個領域事件發生在投保和收款微服務之間。發生的領域事件是:繳費通知單已生成。下一步的業務操作是:繳費。

事件起點:出單員生成投保單,核保通過后,發起生成繳費通知單的操作。
1. 投保微服務應用服務,調用聚合中的領域服務 createPaymentNotice 和 createPaymentNoticeEvent,分別創建繳費通知單、繳費通知單事件。其中繳費通知單事件類 PaymentNoticeEvent 繼承基類 DomainEvent。

2. 利用倉儲服務持久化繳費通知單相關的業務和事件數據。為了避免分布式事務,這些業務和事件數據都持久化到本地投保微服務數據庫中。

3. 通過數據庫日志捕獲技術或者定時程序,從數據庫事件表中獲取事件增量數據,發布到消息中間件。這里說明:事件發布也可以通過應用服務或者領域服務完成發布。

4. 收款微服務在應用層從消息中間件訂閱繳費通知單事件消息主題,監聽並獲取事件數據后,應用服務調用領域層的領域服務將事件數據持久化到本地數據庫中。

5. 收款微服務調用領域層的領域服務 PayPremium,完成繳費。

6. 事件結束。

提示:繳費完成后,后續流程的微服務還會產生很多新的領域事件,比如繳費已完成、保單已保存等等。這些后續的事件處理基本上跟 1~6 的處理機制類似。

總結

領域事件驅動是很成熟的技術,在很多分布式架構中得到了大量的使用。領域事件是 DDD 的一個重要概念,在設計時我們要重點關注領域事件,用領域事件來驅動業務的流轉,盡量采用基於事件的最終一致,降低微服務之間直接訪問的壓力,實現微服務之間的解耦,維護領域模型的獨立性和數據一致性。

除此之外,領域事件驅動機制可以實現一個發布方 N 個訂閱方的模式,這在傳統的直接服務調用設計中基本是不可能做到的。


免責聲明!

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



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