l

2021年6月23日 星期三

領域驅動設計學習筆記(17):學DCI,重構Aggregate ,Part 2

June 23 10:03~12:28

▲圖1:Board Bounded Context領域模型,只表示Board與Workflow這兩個Aggregate的關係。


前言

上一集介紹DCI架構以及它與Clean Architecture與DDD Aggregate Root的比較,今天說明如何藉由DCI的精神來重構Aggregate Root。Teddy將以ezKanban系統中,Board Bounded Context的Board Aggregate Root為例,介紹這個重構過程。

***

重構前的Board Aggregate

▲圖2:Board重構前程式碼片段,完整的程式有七百多行


圖1為Board Aggregate類別圖,圖2為Board重構前的程式碼片段,顯示其資料成員:

  • id:繼承自AggregateRoot物件,紀錄Board id。
  • nam:紀錄Board的名稱
  • teamId:紀錄Board屬於哪個Team
  • boardMembers:紀錄Board身上有哪些成員。
  • committedWorkflows:紀錄Board身上有哪些Workflow。Workflow是另一個Aggregate,代表看板上的工作流程。
  • boardSessions:一位使用者進入Board之後會建立一個board session,透過board session讓位在同一個Board的不同使用者可以互動。

DCI架構要求物件不需要太聰明,只需是Data object即可,至於原先物件身上的方法則抽離出來定義在Role(角色)物件身上。接下來說明如何將Board的角色與資料抽離出來。


***

定義Role介面

根據Board所操作的資料,Teddy發現Board扮演了五種角色:

  • Content:Board的內容管理,包含board id, board name。
  • Dependency:管理Board與其他Aggregate的相依性,在此範例中只有team id。
  • Membership:Board成員管理,可以新增、刪除、查詢成員。
  • Workflow Relation:Board與Workflow關係管理,可以新增、刪除與調整Workflow順序。
  • Collaboration:管理Board身上的同時上線成員。

接著新增KanbanBoardRoles interface,在裡面定義這五種角色,如圖三所示。

▲圖3:用Java interface定義Board的五種角色

***

定義Data介面

定義好角色之後很清楚發現每一種角色只使用到原本Board身上的特定資料成員,因此圖2宣告資料成員的方法很明顯的違反了介面隔離原則。所以針對每一種角色使用到的資料,在該角色內再定義一組存取資料的State介面,如圖4所示。


▲圖4:在每一個角色介面內定義一個存取資料的State介面


最後宣告一個BoardState介面,讓它繼承每一個定義在Role裡面的Role-Based State介面,如圖5所示。

▲圖5:宣告一個包含所有Board資料的BoardState介面,要用它來取代原本宣告於Board身上的資料成員

***

實作角色

角色介面與資料介面都定義好,就可以撰寫實作角色的程式碼。說是實作但其實並非重頭開始寫,而是重構原本的Board Aggregate。重構的方式很簡單,把原本已經寫好放在Board身上分屬於不同角色的方法,搬移到各個角色身上就差不多大功告成了。Teddy把實作角色的類別稱為Behavior類別,圖6為MembershipBehavior類別程式片段,原本joinAs方法放在Board身上,重構方式只是把它從Board身上移到MembershipBehavior身上。

由於joinAs方法原本定義在Board身上,會存取Board資料與方法,也就是說joinAs依賴Board。資料的依賴關係由第24行的State介面所隔離,行為的依賴則是由第23行MixinContext類別所隔離。State介面已於前述段落中說明,MixinContext留待下一集說明如何讓Data object支援動態扮演角色的時候再解釋。


▲圖6:MembershipBehavior類別實作Membership角色介面

***

重構Board

每一個角色都實作完畢之後,最後一步就是重構Board類別。參考圖7:

  • 23~26行:讓Board實作圖3所定義的五個角色,以便讓它對外維持原本的介面。
  • 28行:用BoardState取代原本的資料成員,此時BoardState相當於DCI的Data。
  • 29~32行:圖3定義了五個角色,其中DependencyRole因為只有一個getBoardId方法,所以直接實作在Board身上。其他四個角色各用一個類別實作。29~32行宣告這四個角色的資料成員,Board採用composition-deligation的方式,將客乎端呼叫Board身上不同角色的方法,轉呼叫個別的角色實作物件,請參考圖8。


▲圖7:重構後的Board資料成員


▲圖8:Board身上的joinAs方法轉呼叫membership角色的joinAs方法

重構完畢後Board程式碼剩下約190行(原本有7百多行)。

***

結論

依據DCI「精神」重構之後,Board Aggregate的責任與介面變得很乾淨,個別角色所負擔的責任其實做程式碼也跑到相對應的行為類別身上,符合單一責任原則與介面隔離原則。可以針對個別行為單獨撰寫單元測試,增加了可測性。

DCI架構還有一個很神奇的地方,就是希望Data object可以在不同的Context(使用案例)中動態扮演不同的角色,以執行不同的功能。Teddy將於下一集說明如何用Java語言在DDD Aggregate中實作這個動態扮演角色的特性。

***

友藏內心獨白:真,單一責任原則。

沒有留言:

張貼留言