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使用。
***
結論
釐清邊界,單一責任,分層負責,系統的結構才會清楚明白,軟體也才能變軟。
***
友藏內心獨白:把一件事情做到最好就很厲害了。