設為“星標”,好文章不錯過!
1 定義
將領域中所發生的活動建模成一系列的離散事件。每個事件都用領域對象來表示。
領域事件是領域模型的組成部分,表示領域中所發生的事情。
一個領域事件將導致進一步的業務操作,在實現業務解耦的同時,還有助於形成完整的業務閉環。
領域事件可以是業務流程的一個步驟,比如一個事件發生後觸發的後續動作,比如密碼連續輸錯三次,觸發鎖定賬戶的動作。
2 識別領域事件的話術
“如果發生……,則……”
“當做完……的時候,請通知……”(這裡的通知本身並不能構成一個事 件,而只是表明我們需要向外界發出通知。)
“發生……時,則……”等
“如果發生 這樣的事情,它並不重要;如果發生那樣的事情,它就很重要了”
在這些場景中,如果發生某種事件後,會觸發進一步的操作,那麼這個事件很可能就是領域事件。由於領域事件需要發布到外部系統,比如發布到另一個限界上下文。由於這樣的事件由訂閱方處理,它將對本地和遠程上下文產生深遠的影響。
那領域事件為什麼要用最終一致性,而不是傳統SOA的直接調用?
聚合的一個原則:一個事務中最多只能更改一個聚合實例。所以
本地限界上下文中的其他聚合實例便可以通過領域事件的方式同步
用於使遠程依賴系統與本地系統保持一致。解耦本地系統和遠程系統還有助於提高雙方協作服務的可伸縮性。
聚合創建並發布事件。訂閱方可以先存儲事件,然後再將其轉發到遠程訂閱方,或不經存 儲,直接轉發。除非MQ共享了模型的數據存儲,不然即時轉發需要XA。
考慮在系統非高峰時期,批處理過程通常進行一些系統維護工作,比如刪除過期對象、創建新對象以支持新業務需求或通知用戶所發生的重要事件。這樣的批處理過程通常需複雜 查詢且需龐大事務支持。若這些批處理過程存在冗餘會怎麼樣?
系統中發生的每一件事情,我們都用事件形式捕獲,然後將事件發布給訂閱方處理,能簡化系統嗎?
肯定的!它可消除先前批處理過程中的複雜查詢,因為我們能夠準確知道在何時發生何事,限界上下文也由此知道接下來應該做啥。在接收到領域事件時,系統可立即處理。原本批量集中處理的過程可以分散成許多粒度較小的處理單元,業務需求也由此更快滿足,用戶也可及時進行下一步操作。
領域事件驅動設計可以切斷領域模型之間的強依賴關係,事件發布完成後,發布方不必關心後續訂閱方事件處理是否成功,這樣可以實現領域模型的解耦,維護領域模型的獨立性和數據的一致性。在領域模型映射到微服務系統架構時,領域事件可以解耦微服務,微服務之間的數據不必要求強一致性,而是基於事件的最終一致性。
有的領域事件發生在微服務內的聚合之間,有的則發生在微服務之間,還有兩者皆有的場景,一般來說跨微服務的領域事件處理居多。在微服務設計時不同領域事件的處理方式會不一樣。
3 微服務內
當領域事件發生在微服務內的聚合間,領域事件發生後完成事件實體構建和事件數據持久化,發布方聚合將事件發布到事件總線,訂閱方接收事件數據完成後續業務操作。
微服務內大部分事件的集成,都發生在同一進程,進程自身可很好控制事務,因此不一定需要引入MQ。但一個事件若同時更新多個聚合,按“一次事務只更新一個聚合”原則,可考慮引入事件總線。
微服務內應用服務,可通過跨聚合的服務編排和組合,以服務調用的方式完成跨聚合訪問,這種方式通常應用於實時性和數據一致性要求高的場景。這個過程會用到分布式事務,以保證發布方和訂閱方的數據同時更新成功。
在微服務內,不是少用領域事件,而是推薦少用事件總線。在DDD中是以聚合為單位進行數據管理,若一次操作會修改同一微服務內的多個聚合的數據,就需保證多個聚合的數據一致性,為了解耦不同聚合,需採用分布式事務或事件總線兩種方式,用事件總線不太方便管理服務和數據的關係,可用類似saga之類的分布式事務技術。總之需要確保你的不同聚合的業務規則和數據一致性,盡量減少系統建設複雜度。
4 微服務間
跨微服務的領域事件會在不同限界上下文或領域模型間實現業務協作,主要都是為解耦,減輕微服務間實時服務訪問壓力。
領域事件發生在微服務間較多,事件處理機制也更複雜。跨微服務事件可推動業務流程或數據在不同子域或微服務間直接流轉。
跨微服務的事件機制要總體考慮事件構建、發布和訂閱、事件數據持久化、MQ,甚至事件數據持久化時還可能需考慮引入分布式事務。
微服務間訪問也可採用應用服務直接調用,實現數據和服務的實時訪問,弊端就是跨微服務的數據同時變更需要引入分布式事務。分布式事務會影響系統性能,增加微服務間耦合,盡量避免使用。
5 領域事件總體架構
5.1 事件構建和發布
事件基本屬性
至少包括:
事件唯一標識(全局唯一,事件能夠無歧義在多個限界上下文中傳遞)
發生時間
事件類型
事件源
即主要記錄事件本身以及事件發生背景的數據。
業務屬性
記錄事件發生那一刻的業務數據,這些數據會隨事件傳輸到訂閱方,以開展下一步業務操作。
事件基本屬性和業務屬性一起構成事件實體,事件實體依賴聚合根。領域事件發生後,事件中的業務數據不再修改,因此業務數據可以以序列化值對象的形式保存,這種存儲格式在消息中間件中也比較容易解析和獲取。
為保證事件結構的統一,還會創建事件基類 DomainEvent,子類可以擴充屬性和方法。由於事件沒有太多的業務行為,實現方法一般比較簡單。
事件發布之前需要先構建事件實體並持久化。事件發布的方式有很多種
可通過應用服務或者領域服務發布到事件總線或MQ
也可從事件表中利用定時程序或數據庫日誌捕獲技術獲取增量事件數據,發布到MQ
5.2 事件數據持久化
意義
系統之間數據對賬
實現發布方和訂閱方事件數據的審計
當遇到MQ、訂閱方系統宕機或網絡中斷,在問題解決後仍可繼續後續業務流轉,保證數據一致性。
實現方案
持久化到本地業務DB的事件表,利用本地事務保證業務和事件數據的一致性
持久化到共享的事件DB。業務、事件DB不在同一DB,它們的數據持久化操作會跨DB,因此需分布式事務保證業務和事件數據強一致性,對系統性能有影響
5.3 事件總線(EventBus)
意義
實現微服務內聚合間領域事件,提供事件分發和接收等服務。
是進程內模型,會在微服務內聚合之間遍歷訂閱者列表,採取同步或異步傳遞數據。
事件分發流程
若是微服務內的訂閱者(其它聚合),則直接分發到指定訂閱者
微服務外的訂閱者,將事件數據保存到事件庫(表)並異步發送到MQ
同時存在微服務內和外訂閱者,則先分發到內部訂閱者,將事件消息保存到事件庫(表),再異步發送到MQ
5.4 MQ
跨微服務的領域事件大多會用到MQ,實現跨微服務的事件發布和訂閱。
雖然MQ自身有持久化功能,但中間過程或在訂閱到數據後,在處理之前出問題,需要進行數據對賬,這樣就沒法找到發布時和處理後的數據版本。關鍵的業務數據推薦還是落庫。
5.5 事件接收和處理
微服務訂閱方在應用層採用監聽機制,接收MQ中的事件數據,完成事件數據的持久化後,就可以開始進一步的業務處理。領域事件處理可在領域服務中實現。
有同學會問了,事件有沒有被消費成功(消費端成功拿到消息或消費端業務處理成功),一般如何通知到消息生產端?
因為事件發布方有事件實體的原始的持久化數據,事件訂閱方也有自己接收的持久化數據。一般可以通過定期對賬的方式檢查數據的一致性。
6 總結
今天我們主要講了領域事件以及領域事件的處理機制。領域事件驅動是很成熟的技術,在很多分布式架構中得到了大量的使用。領域事件是DDD的一個重要概念,在設計時我們要重點關注領域事件,用領域事件來驅動業務的流轉,盡量採用基於事件的最終一致,降低微服務之間直接訪問的壓力,實現微服務之間的解耦,維護領域模型的獨立性和數據一致性。
除此之外,領域事件驅動機制可以實現一個發布方N個訂閱方的模式,這在傳統的直接服務調用設計中基本是不可能做到的。
參考
《實現領域驅動設計》
領域事件:解耦微服務的關鍵
《領域驅動設計》