上一篇我們討論DDD的戰略設計,說明系統範圍如何切割成多個領域(Domain)、子領域(Sub-Domain),完成後,接著進行戰術設計,將實體進一步分類,並訂定技術架構,提供服務以操作這些實體。
Eric Evans 描述DDD的工作範圍如下,其中上半部為戰術設計,下半部為戰略設計:
圖一. DDD 戰術設計
依照上圖,戰略設計(Model-driven design)要進行的工作如下:
為達到『高內聚、低耦合』(High conhesion、Low Coupling)的設計原則,會將各個實體歸類成一個個的聚合,例如客戶、訂單(保單)、產品...等,客戶聚合包括客戶主體、多個帳戶實體、多個分公司地址,其中客戶主體稱為聚合根(Aggregate Root),它具有唯一的識別欄位(ID),可以存取單一實例(instance),聚合就像是肉粽串,而聚合根就是草繩的頭,往上提就可以把聚合內所有的實體都抓起來,因此,聚合根是一個帶有業務規則的實體,是對外溝通的窗口。
因此,我們會將一個領域或子領域細分為多個聚合,每個聚合有唯一的聚合根,秉持『高內聚、低耦合』的原則,盡量減少聚合間的連結。
值物件是表達實體的狀態(State),沒有識別的Id,例如訂單上客戶的地址,它是訂購當時客戶的地址,可能不是最新的,或是訂單上的總價或折扣,只是計算欄位,是一個快照(Snapshot)的概念,因此,一個龐大的實體盡量能拆成多個值物件,會使實體變得更容易維護,因為值物件是不可修改的(Immutable),要嘛整個刪除或是重新建立,例如下圖。
圖二. 實體(Entity) vs. 值物件(Value Object),信用報告含多個值物件
我們使用各種 ORM、MVC 或 MVVM 架構撰寫應用程式時,系統會要求我們建立類別,與資料庫的資料表/欄位對應,這就是所謂的Persistent Object(PO),類別內只有屬性,沒有方法,稱為POJO(Java)或POCO(C#),會讓人誤以為這就是實體,依 DDD 的設計原則來看,這是不好的『貧血模型』(Anemic Domain Model),它會使操作物件的方法全部寫在類別外的程式邏輯中,違反物件導向設計原則(OOP)、單一職責設計原則(Solid),使程式邏輯全部攪在一起,難以維護,因此,我們應該在Persistent Object上,重新組織一層 Domain Object,將相關的PO連結在一起,並且賦予相關行為的描述,即充血模型,業務邏輯應放類別裡面,例如MVVM的View Model就是Domain Object(DO),它可以是畫面(View)的資料來源,也可能是API的輸入/輸出(REST、WSDL)、網路傳輸的資料內容(Data Transfer Object, DTO)。
在程式開發時 DO 與 PO 的互轉非常頻繁,筆者使用C#開發程式時,就常使用 AutoMapper 函數庫,進行 DO 與 PO 的轉換。譬如,從請購單、採購單、到貨通知單、驗收單,多張請購單可能併成一張採購單,多張採購單可能一次到貨,也可能分批到貨,驗收時也是如此,這些單據如果都是一個物件,每個物件都會影響其他物件的狀態及內容。
DDD 建議引用各種設計模式,提高生產力,例如 Repository/Factory簡化物件產生/共通方法操作,Adapter方便模組掛載,PPublish/Subscribe處理事件的傳送/接收,目前各種語言都可以找到相關的框架(Framework)或函數庫,可以直接套用,後續我們就來看看Python相關的套件。
DDD 並沒有嚴格規定進行的步驟,只有一些原則(Principle),每本書籍的作者各有領悟,利用上述的原則,自行訂定詳細的執行步驟,大部份都搭配Java框架,筆者推薦『中台架構與實現:基於 DDD 和微服務』一書的說明,不過,筆者並不喜歡Java繁複的架構,所以,想使用Python試試看,它又可以輕易的整合機器學習模型。