l

2019年12月26日 星期四

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

Dec. 26 17:30~18:47

圖1:看板系統的部分領域模型


背景介紹

為了明年泰迪軟體新修訂課程【領域驅動設計與簡潔架構入門實作班】,Teddy最近三個禮拜都在修改課程專案的程式範例。簡單的說,Teddy想藉由開發一個看板系統,來達到學習領域驅動設計(Domain-Driven Design;DDD)、簡潔架構(Clean Architecture)以及實例化規格(Specification by Example;SBE)。

這一系列談用DDD實作系統時如何發出與處理領域事件(domain event),這一集先談Aggregate Root如何發送領域事件,下一集再談如何接收。

圖1是Teddy設計的CleanKanban系統的部分領域模型,包含了兩個Aggregate:

  • Board:代表一個看板,一個Board可以包含多個Workflow。這個aggregate由Board entity以及 CommittedWorkflow value object所組成,Board為Aggregate Root。
  • Workflow:代表工作流程,其中包含了Stage(垂直的工作步驟)以及Swimlane(水平的工作步驟),Workflow、Stage、Swimlane都是entity,Workflow為Aggregate Root。


圖2:新增Stage的時候Workflow會發出 Stage Created領域事件。

如圖2所示,如果使用者要新增一個Stage(垂直的工作階段),由於Stage屬於Workflow這個aggregate,所以Add Stage的要求會交由aggregate root,也就是Workflow來處理。

接下來看程式碼,如何送出領域事件。

***

Aggregate Root送出領域事件

有三種常見的方式可以送出領域事件:

  • 使用Publisher Subscriber設計模式,領域事件發生時立刻傳送出去。
  • 使用回傳值,呼叫產生領域事件的方法(method or function)之後傳回領域事件。
  • 將領域事件先存在Aggregate Root身上,等適當時機再發送。

Teddy試過第一種和第三種方式,覺得第三種方式比較好。接下來介紹第三種方式。

圖3:AggregateRoot抽象類別程式碼

步驟一:首先宣告一個AggregateRoot抽象類別,如圖3所示。在它上身有一個List<DomainEvent> domainEvents資料成員,用來儲存準備對外發送的領域事件。


圖4:Workflow類別的addStage方法


步驟二:接下來看Workflow的addStage方法,它負責產生一個新的Stage,並將該Stage加在它身上。完成之後Workflow產生一個StageCreated領域事件,並呼叫addDomainEvent方法把這個領域物件先存起來。


圖五:由使用案例層發出領域事件

步驟三:由於Teddy套用Clean Architecture,在實作Add Stage Command的時候直接把它變成一個Use Case類別,如圖五所示的CreateStageUseCase

CreateStageUseCase的execute方法,首先透過repository找出要在哪個Workflow新增Stage,然後呼叫該Workflow的addStage方法,接著把Workflow透過repository存起來。最後,呼叫eventBus的postAll方法將Workflow身上的領域事件全部送出。因為已經將Workflow狀態儲存起來,此時發出領域事件是很合適的時機。


圖六:DomainEventBus

至於傳送領域事件的方式,Teddy目前使採用Google開發的一個開源軟體元件叫做EventBus,然後稍微加工一下符合自己的需求, 請參考圖六。

這種寫法違反了Clean Architecture的相依性原則,需要自行定義一個介面,將對於EventBus的相依性反轉過來。這個就留給鄉民當作練習XD。

***

友藏內心獨白:領域事件先存起來稍後再發送比較好。

沒有留言:

張貼留言