l

2020年7月17日 星期五

領域驅動設計學習筆記(9):Query和Read Model

July 17 12:40~13:48

▲圖1:解釋所有事情的一張圖,取自《Introducing EventStorming


背景介紹

在Alberto Brandolini的《Introducing EventStorming》書中,有一張圖用來解釋他對於採用event storming塑模問題的想法,如圖1。


▲圖2:解釋所有事情的一張圖,取自《Introducing EventStorming


把圖1用event storming展開,出現如圖2所示的流程:

  1. 使用者執行Aggregate身上的Command。例如,在看板系統中,使用者新增看板(Create Board)、移動卡片(Move Card)。
  2. Aggregate執行完Command後發出Domain Event,用來代表系統狀態發生改變。例如看板已新增(BoardCreated)、卡片已移動(CardMoved),
  3. Domain Event也可能由External System所產生,例如,收到銀行傳來的付款確認(PaymentConfirmed)事件。
  4. 剛剛上述行為屬於Write Model,也就是執行Command改變系統狀態並產生領域事件。但使用者不只會改變系統狀態,也需要讀取系統資訊,以決定要執行哪一個Command。表達讀取資訊的模型稱為Read Model,它可以從改變狀態的領域事件而轉換出Read Model。
  5. 將這個Read Model傳給UI,可以做出最後使用者看到的系統畫面。
  6. 使用者看到系統畫面,或更抽象的表示,看到Read Model,從而做出決定,執行下一個Command。
  7. 圖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我們所關心的問題就好。

2020年7月7日 星期二

領域驅動設計學習筆記(8):幫Event Handler取個好命字

July 07 13:32~14:50

▲幫Event Handler取個好名字


最終一致性(Eventual Consistency)

領域驅動設計(Domain-Driven Design;DDD)引入Aggregate,更新同一個Aggregate需要保證交易處裡的一致性達到ACID要求。跨Aggregate之間則是透過領域事件通知,達到最終一致性(eventual consistency)即可。


▲圖1:Board與Workflow兩個Aggregate


以cleanKanban系統為例,圖1表示兩個Aggregate—Board與Workflow的關係,一個Board擁有零到多個Workflow。因為兩者是獨立的Aggregate,當新增Workflow之後,Board需要收到WorkflowCreated領域事件通知,才能夠建立它與Workflow之間的關係,如圖2所示。


▲圖2:Aggregate之間透過領域事件更新狀態


***

取名字

圖2是Event Storming(事件風暴)產出圖表,Event Storming定義了Domain Event、Command、Aggregate、Read Model、Policy、External System等便利貼,但是沒有代表處裡事件的Event Handler。

基本上Event Handler是一種程式實作細節,是落實Policy的實作方式。為了落實DDD的ubiquitous language in code,Teddy希望Event Storming也可以用表達Event Handler的便利貼,以便於實作時可以看著Event Storming圖表來寫程式碼。


▲圖3:新增不同顏色便利貼並直接用EventHandler當名字


Teddy一開始嘗試增加一種新顏色的便利貼,並把它貼在要透過Domain Event達到最終一致性的兩個Aggregate之間。如圖3所示,WorkflowEventHandler負責聽取WorkflowCreated領域事件,然後呼叫Commit Workflow使用案例建立起Workflow與Board之間的關係。

現在試著讀一下這個模型的通用語言:


Teddy執行Create Workflow使用案例,執行完畢後產生Workflow Created領域事件。WorkflowEventHandler一收到Workflow Created,執行Commit Workflow建立剛剛新增的Workflow與它所屬Board的關聯。