l

2020年1月1日 星期三

領域驅動設計學習筆記(7):Aggregate (下)

January 01 21:52~23:28

▲圖1:Clean Kanban領域模型(部分)


問題

領域驅動設計(Domain-Driven Design;DDD)的Aggregate物件之間透過ID(字串或GUID)參考,而不像傳統物件導向設計(OOAD)直接透過記憶體參考(memory reference)來存取依賴物件。對於「從小」熟練物件導向分析與設計(OOAD)的Teddy而言,剛開始覺得這樣做好像不太方便啊。只記錄Aggregate ID,還要透過Repository來取得該Aggregate物件,這樣不是有點麻煩嗎?

***

記憶體參考

圖1是Teddy所開發的看板系統的部分領域模型:

  • Board:代表一個看板。
  • Workflow:代表一個工作流程,一個看板可以有多個工作流程。
  • Stage:代表垂直的工作階段。
  • Swimlane:代表水平的工作階段。

如果領域模型直接透過記憶體參考,也不套用Aggregate,寫程式的時候就可以:

  • board.getWorkflows()傳回看板的全部工作流程物件,或用board.getworkflowsById()得到某個工作流程物件。
  • workflow.getBoard()得到這個看板所屬的board物件。

這樣一來透過物件的reference可以輕鬆自在存取整個領域模型,甚至可以:

stage.getworkflow().getBoard().getUser()……一直不斷的get下去。當然程式寫成這樣就產生壞味道,但是無限制的記憶體參考很容易「引誘犯罪」,讓開發人員寫出這種程式。

***

Aggregate形成邊界

圖2:套用Aggregate形成物件的邊界

如圖2所示,套用DDD的Aggregate設計模式,原本領域物件變成兩個Aggregate:Board與Workflow,得到如圖3所示的Board與Workflow類別。

圖3:Board與Workflow類別。

套用Aggregate有幾個好處:

  • Board與Workflow變成獨立的Aggregate,因此可以各自更新,不必放在同一個交易(transaction)裡面。如此一來可以提高並行處理的彈性。
  • 一個Aggregate對應一個Repository,物件儲存問題變得簡單很多。
  • Aggregate內部物件的存取必須透過Aggregate Root,因此可以讓Aggregate Root來確保對於Aggregate的操作不會違反invariant,也就是保持程式狀態的正確性。

當然有好處也有缺點:

  • 最大的缺點(也是優點)可能就是Aggregate之間透過領域事件(domain event)達成狀態同步,由原本同步的方式變成非同步方式,程式開發模式跟傳統不同,習慣需要改變一下,重新學習。
  • 無法像傳統一樣,只要透過物件參考就可以得到整個領域物件(或大部分領域物件)的資料。例如,如果使用者介面要顯示看板所有的工作流程,從Board物件無法直接得到Workflow物件,因為Board只記錄Workflow的ID,必須再透過WorkflowRepository才能得到Workflow物件。

***

View Model

▲Clean Architecture,圖片來源在此

將領域模型與使用者介面模型(View Model)切開,原本就是DDD的重點。但是領域模型最終還是要展示給使用者看,此時只要多設計一層View Model,將領域模型轉成使用者介面方便操作的View Model就可以了。從Clean Architecture的角度來看,讓Interface Adapter層(第三層)來負責做轉換。

▲設計BoardDto給UI使用。

***

結論

釐清邊界,單一責任,分層負責,系統的結構才會清楚明白,軟體也才能變軟。

***


友藏內心獨白:把一件事情做到最好就很厲害了。