l

2019年12月27日 星期五

領域事件的發送與接收(下)

Dec. 27 06:40~09:02

▲圖1:兩個Aggregate透過領域事件同步狀態


前情提要

上一集談到Teddy開發的Clean Kanban系統範例其中的兩個Aggregate:Board與Workflow,如圖2所示。


▲圖2:Board與Workflow aggregates。

Workflow的addStage方法會發出StageCreated領域事件,先將領域事件儲存在身上,等待儲存Workflow之後再一起發送領域事件。

今天要介紹如何接收並且處理領域事件。

***

誰來接收領域事件

Board與Workflow被設計成兩個Aggregate,一個Board包含零到多個Workflow,Board依靠CommittedWorkflow Value Object來存儲它與Workflow關係。

如圖1所示,新增Workflow會產生Workflow Created領域事件,這個事件驅動Commit Workflow這個Command,通知Board將剛剛新增的的Workflow加入自己身上(透過新增一筆CommittedWorkflow的方式來紀錄)。

有兩種可能的方式來接收WorkflowCrated領域事件:

  • Board:既然Board對於WorkflowCrated領域事件感興趣,可以讓它直接接收這個領域事件。但是為了處理領域事件,Board可能需要透過Repository讀取其他物件,如此一來會讓Board與Repository產生相依性。依據Clean Architecture,Board屬於最內層的Entity Layer,而Repository屬於第二層Use Case Layer,因此Teddy不考慮讓Board直接去接收領域事件。
  • Event Handler:透過位於Use Case Layer的event handler來接收領域事件,接下來將採用此方式接收與處理領域事件。

***

程式範例

▲圖3:Workflow Event Handler程式範例

Event handler程式很容易撰寫,上一集提到Teddy使用EventBus元件來發送與接收領域事件,EventBus採用annotation的方式來標註event handler,如圖3所示,只要在處理領域事件的method身上貼上@Subscribe,然後再跟EventBus註冊即可。

註冊event handler的方式如圖4所示,呼叫EventBus的register方法即可。

▲圖4:註冊event handler

***

▲圖5:測試案例

圖5為測試案例,在setUp方法中記得先註冊event handler,然後驗證產生Workflow會在Board物件上將新增一筆CommittedWorkflow資料。測試案例通過代表event handler產生作用。

***

不是新東西

把Aggregate當成狀態機(State Machine),透過Command產生狀態改變(state transition)並發出事件,讓對於狀態改變有興趣的物件(也就是event handler)可以依據這些事件做出反應,並不是什麼新的技術,也不是領域驅動設計(DDD)或是Event Storming所獨創的開發方法。這種程式開發方式在「古早」時代就有了

從實作面來看,只要套StateObserver這兩個設計模式就可以做到。所以,不要被名詞給嚇到,要關注這些名詞背後所要解決的問題是什麼

DDD加上Event Storming在這裡的價值在於,前者提出Aggregate模式,讓開發者可以組織一個合適的物件群組邊界(a cluster of objects)。不同的Aggregate之間不再透過直接記憶體物件參考(object reference via memory address)而是透過事件(領域事件)來同步狀態。只有Aggregate內的狀態保存需要符合ACID(atomicity、consistency、isolation、durability),Aggregate之間的狀態放寬到最終一致性(eventual consistency)即可。

放寬了consistency model之後,除了可以提升程式的並行性,對於資料庫設計也簡化許多。不需要再像傳統系統一樣,使用一個大型關聯式資料庫來確保所有資料的一致性。系統可以切割成不同的模組、服務、微服務,或是用DDD的說法,Bounded Context,簡化了每個Bounded Context的設計、開發、佈署與維運等工作。

每一個Bounded Context之內的不同Aggregate透過Domain Event來同步狀態,不同的Bounded Context則是透過跨Bounded Context的Domain Event來同步狀態(也就是說Domain Event可以區分為Bounded Context之內與之外這兩種)。

既然系統演變成事件驅動的模式,傳統物件導向分析設計以名詞(先找概念)為主的塑模方式,就變得比較不那麼直覺。Event Storming,嚴格說起來,它的基本元素在傳統物件導向分析設計裡面幾乎都有,但是Teddy覺得Event Storming有幾點很不一樣的地方:

  • 採用以事件(領域事件)為主的塑模方式,不但在分析階段可以優先關注商業流程,在開發階段也可以自然配合事件驅動的系統架構。透過一個「無限大的牆面」,以迭代與增量的方式讓開發團隊與領域專家一起合作、學習、交換知識、產生共同語言。
  • 把傳統物件導向分析設計所需繪製的很多圖表,用一些很簡單的圖示來取代。請參考圖6,而這個圖中的元素不但可以用在分析商業流程(Big Picture Event Storming),還可以用在設計(Design Level Event Storming),簡單、好記、好用,又有足夠的表達力。


▲圖6:Alberto Brandolini 的「picture that explains everything」,節錄自《Event Storming》。

***

友藏內心獨白:不要被名詞嚇到。

沒有留言:

張貼留言