領域事件及事件匯流排EventBus使用實踐
@大鵬開源:別看我有點萌,我可以秒變大鵬
地址:https://github.com/dapeng-soa
DDD 與領域事件
在過去的 30 多年,就已經有領域建模和設計的思潮,Eric Evans將其定義為領域驅動設計(Domain-Driven Design,簡稱DDD)。領域模型是領域驅動的核心,而領域事件又作為領域模型中的重要模塊,解決了開發者日常開發中的很多痛點,如降低代碼耦合,增強拓展性。
領域模型不是高大上的東西,所有的領域模型抽象都來自於具體的業務,而脫離業務需求的應用設計是沒有任何價值的。
在Today的新零售架構中:門店、採購、訂單、供應商、物流、商品、台賬等等都是應用設計中的不同領域模型,當然還存在很多子域模型,而對於技術人員來說,這些抽象出來的領域,就代表應用架構存在若干子系統。
系統與系統間會存在很多關聯。比如說A領域「發生某件事情」、「當什麼產生變化的時候」、「如果什麼狀態變更」...,都將可能成為B領域所要關心的事件。
事件匯流排
關心一件事,便會收集這件事情相關的信息,而這些都將會轉換為消息流,在訂閱這件事情的領域間傳播。我們將發出事件通知的一方稱為發送者(Publisher),關心事件的一方稱為訂閱者(Subscriber)。
以上eventbus示意圖大致流程是這樣的:
服務介面觸發事件。
eventbus 分發事件,如果存在領域內訂閱者,直接分發到指定訂閱者,再將事件消息存庫定時發送至 kafka ;如果不存在領域內訂閱者,事件消息直接存庫並定時發送 kafka。
消息在發送成功以後會被清除,為了保證事務的一致性,建議事件db共享業務數據源。
訂閱者只需要訂閱事件雙方規約好的 topic 和事件類型就可以命中需要的事件消息。
引入事件的依據
很多業務場景下,我們可能需要在某件事情完成後,根據業務完成狀態來做業務路由。
比如說商品領域的的變價審核業務,在商品變價審核通過之後,對應的商品價格也隨之生效;價格的變動可能會引起採購、供應商、門店等領域相應作出調整。
而我們在代碼中通常這樣去描述與以上類似的業務:
從上面可以看出,當主線業務遭遇某個狀態時,需要第三方系統作出應對。若我們在主線任務中加入了較長的代碼甚至引入別人的 api ,這會使得單個業務變的臃腫、過度耦合、不易閱讀,這時領域事件可以幫助我們更加優雅的解決這個問題。
當引入事件後,do A 將變成了 send eventA。
事件匯流排實踐
在 today 中台服務團隊的各領域實踐中,為了給第三方系統和本部門的業務開發人員提供一致性的開發體驗,我們將事件匯流排從dapeng的框架中剝離出來, 單獨提供了一套類庫用於實現事件的發布以及訂閱。
事件匯流排eventBus的核心庫
事件內容及狀態暫存支持
在業務資料庫加入一張如下結構數據表,作為事件消息的暫存隊列和事件發送狀態存儲表。
在具體開發中實現事件發送與訂閱需要四步:
定義事件結構體;
在服務介面方法中聲明待發布的事件;
通過EventBus發布事件;
通過EventBus接收事件。
下面以商品變價審核的狀態變更為例,具體說明一下每個步驟。
事件發送與訂閱
定義事件結構體
1.事件收發雙方共同協定事件消息的內容, 一個領域的所有消息定義都在同一個獨立的idl文件中, 這個idl文件應該放在發布者的API包中。
2.事件對象需要定義一個事件 id (建議通過分散式取號服務來獲取), 訂閱者可以自己決定是否需要用這個事件 id 來做消息的冪等處理。
聲明待發布事件
秉承代碼及文檔一致的理念,所有的服務都會在統一的文檔站點進行開放展示,每個服務和介面的描述,包括出入參都一目了然。我們在服務介面方法裡面聲明需要發布的事件,這些事件清單將會在文檔站點對應的服務方法中得到展示,減少開發人員的溝通成本。
在文檔站點方法上效果如下:
顯示獨立的事件清單
註:若想要了解更多有關文檔站點的內容,請留意後期的 dapeng 文檔站點專題。
定義事件發布任務
定義事件發布任務idl
為發布任務服務提供以下實現模版
關鍵性的bean配置
所有的事件消息,最終都會發送到 kafka 的隊列中,等待訂閱者消費,所以每一個配置都將必不可少。
:kafka 消息 topic,領域區分(建議: )。
:kafka 集群地址(如:127.0.0.1:9091,127.0.0.1:9092)。
:kafka 事務 id 前綴,領域區分。
:使用業務的 dataSource。
通過EventBus發布事件
在事件觸發前,需要實現 ,實現自定義的本地監聽分發。
交由 spring 託管
事件發布
通過EventBus接收事件
對於領域內事件訂閱者
的 方法提供領域內訂閱者的事件分發,以便本地訂閱者可以訂閱到關注的事件消息。這些領域內的訂閱者,只需要在 中模式匹配進行分發。
對於跨領域事件訂閱者
依賴:針對其他領域服務及第三方系統,提供了一致的 api。
註解掃描支持配置
訂閱事件消息:同一個領域的事件在同一個消費者類中處理。
也需要在 spring 上下文中託管。
@KafkaConsumer
: kafka Consumer groupId,領域區分
: kafka 消息 topic
: 可自行配置的 kafka 地址,默認值為 。支持自定義修改,用戶只要負責把這些配置放到 env 或者 properties 里。如:
@KafkaListener
serializer 事件消息解碼器,由事件發送方提供。
通過以上已經知道在事件中,領域內的訂閱者和跨領域的事件訂閱者消費事件消息存在差異:
領域內的事件訂閱者,通常不能脫離領域的存在,存在領域內強關係的,但又需要解耦。
跨領域的事件消息訂閱,通常只需保證最終一致性,他們相對事件發送方沒有強依賴關係。
需要注意的是:在 eventbus 中,領域內消費事件之後還是會將事件消息廣播出去。因為不能保證不會有其他領域對發生的事件感興趣!
如商品領域的商品變價審核通過後,觸發了審核通過事件。事件觸發後將使價格生效,這部分生效操作可以通過領域內的事件訂閱進行解耦。因為更新了商品價格,可能存在庫存系統或者其他業務系統對商品數據敏感,可以通過跨領域事件發送-訂閱,做商品的數據推送。
附:binlog-kafka動態緩存更新支持
Eventbus將訂閱者 api 進行了有趣的拓展,加入binlog-kafka動態緩存更新支持。使用方法與事件的訂閱者方法類似,唯一的不同是不再需要消息解碼器。@BinlogListener
總結
總體來說,不論是事件的發送還是訂閱,對於開發者而言都是易用的,並且沒有多餘的配置。對於第三方系統的支持也做的非常優秀,希望在日常開發中能夠更加靈活的運用,盡量減少不必要的耦合,並能經受實踐考驗!
有關eventBus的具體實現細節,將由小夥伴 hz.lei 來進行剖析!
下期預告:hz.lei:DDD-事件匯流排實現架構原理分析
TAG:TodayTech |