July 17 12:40~13:48
▲圖1:解釋所有事情的一張圖,取自《Introducing EventStorming》
背景介紹
在Alberto Brandolini的《Introducing EventStorming》書中,有一張圖用來解釋他對於採用event storming塑模問題的想法,如圖1。
▲圖2:解釋所有事情的一張圖,取自《Introducing EventStorming》
把圖1用event storming展開,出現如圖2所示的流程:
- 使用者執行Aggregate身上的Command。例如,在看板系統中,使用者新增看板(Create Board)、移動卡片(Move Card)。
- Aggregate執行完Command後發出Domain Event,用來代表系統狀態發生改變。例如看板已新增(BoardCreated)、卡片已移動(CardMoved),
- Domain Event也可能由External System所產生,例如,收到銀行傳來的付款確認(PaymentConfirmed)事件。
- 剛剛上述行為屬於Write Model,也就是執行Command改變系統狀態並產生領域事件。但使用者不只會改變系統狀態,也需要讀取系統資訊,以決定要執行哪一個Command。表達讀取資訊的模型稱為Read Model,它可以從改變狀態的領域事件而轉換出Read Model。
- 將這個Read Model傳給UI,可以做出最後使用者看到的系統畫面。
- 使用者看到系統畫面,或更抽象的表示,看到Read Model,從而做出決定,執行下一個Command。
- 圖2中的Policy表示發生領域事件之後需要後續處理的事情。例如,當使用者註冊成功,則寄發啟動帳戶的email。
***
Event Storming + Clean Architecture
▲圖3:cleanKanban範例,將Read Model簡化成Use Case的Input
Teddy在上〈領域驅動設計與簡潔架構入門實作班〉教導學員用Event Storming來建立Domain Model與Ubiquitous Language。在實作面,套用Clean Architecture,並採用TDD/SBE的方式來撰寫程式。因為Clean Architecture最核心之處在於Entity Layer與Use Case Layer,把UI和DB都當成技術細節,因此在塑模時先不考慮。
所以Teddy把原本Event Storming的綠色便利貼,由原本的Read Model,簡化為使用者看完Read Model之後,決定要執行哪一個Command所給予的參數。例如,顧客去麥當勞櫃台點餐,他看到Menu上面有1號餐、2號餐、N號餐等,這個Menu就是Read Model。最後使用者決定要點一份5號餐,外加一份大薯條,這些資料就是Input。
***
加上Query
如果只是實作後端,從Clean Architecture的角度來看,上面這些分別代表Command、Aggregate、Domain Event、Policy、Read Model(Input)以及UI 的便利貼,也就夠了。但實際開發完整的軟體需要接上前端,此時發現少了一種便利貼:Query。
Command是會改變系統狀態不傳回值的操作,Query則相反,不會改變系統狀態但會回傳值。圖1與圖2中,Domain Event—>Read Model之間,應該還需要補上Query,這樣子參考Event Storming轉成程式碼的時候就可以有一對一的對應關係,如圖4所示。
▲圖4:ezKanban範例,由Query (Get Home Content) 產生Read Model (或稱為View Model),再將此Read Model顯示在UI上。
在圖4中,Query並不像Command需要傳給Aggregate,其實作方式可以透過撰寫Read Repository以及搭配Projection (將原本適合用來寫入的Write Model資料映射成適合讀取的Read Model資料)。
***
結論
加上Query之後,整個Event Storming的便利貼就更完整,也可以和UX/UI設計師討論人機互動的問題。
但是,加上Query與UI,整個模型也變得更加複雜。從軟體架構的角度來看,是否在軟體開發初期就需要增加這樣的複雜度,也是一個可以討論的議題。當然可以只針對Core Domain以及支援商業流程的主要Read Model優先設計畫面,其他次要部份採用迭代與增量的方式來實作,也是一種選擇。
***
友藏內心獨白:Model 不是越詳細越好,只要能表達problem domain我們所關心的問題就好。