# DDD (Domain-Driven Design) 2/10 ## 前言 本篇是以讀 ***产品代码都给你看了,可别再说不会DDD*** (https://docs.mryqr.com/ddd-introduction/),進行介紹。有興趣可以自行查看原文。先從一個單一的產品程式了解DDD 如何運行,後續再進行理解如何與微服務進行串接。 ## DDD概念大白話 ### 戰略設計 vs 戰術設計  根據定義 DDD有兩種設計模式,分別是**戰略設計**以及**戰術設計**。 戰略設計站在較為宏觀的角度去設計/切割系統所需要涵蓋的範疇以及邊界,而戰術設計則是更偏向導入DDD時細節該如何實現。 * 戰略設計 戰略設計的概念相當複雜而且重要,其中DDD中最重要的核心的Domain如何被發掘更是重中之重。本章是為DDD做一個概述,因此在此不細談戰略該如何實現,我們可以在下一章再一併說明,在此只需要知道戰略設計的目標就是 - **解決DDD模組如何劃分**的問題即可。 * 戰術設計 - 領域模型 DDD的重要使命是分離系統中業務以及技術的複雜度,為此我們藉由領域模型來描述業務行為,使其與DB、MQ等技術實現細節分離,而其中最重要的概念則是**聚合根**,甚至不諱言的說,整個DDD都是圍繞聚合根這個概念開展的。 * 領域模型 - 聚合根 聚合根是業務邏輯主要載體,甚至在理想上應該是業務為一載體。但現實就是,有時候將所有業務邏輯放到聚合根內是不合適,甚至是不可行的,像是如果要更新Member的電話號碼前須要檢查這個電話號碼是否已經被綁定的這個檢查就是不適合放進聚合根的業務邏輯,而要處理這類的問題我們就需要使用DDD的另一個概念-**領域服務**(Domain Service)。另外,從更廣義的定義來說聚合根也是屬於實體的一個部分。 * 領域模型 - 領域服務 領域服務雖然也有服務的字眼,卻與傳統SOA或是MVC架構下的服務定義不同。領域服務的目的是處理聚合根無法獨自完成的工作, 如跨領域(聚合)業務或是要訪問基礎設施(DB/MQ/外部系統)之類的場景應用。 * 領域模型 - 實體與值對象(物件) 實體與值對象是一相互對立的物件概念,實體最重要的是具有"生命週期"以及"獨特標示"的概念,而值對象沒有,值對象最重要的概念是將"值"描述出來,舉例來說訂單就是實體,因為單一訂單一定有其存在的生命週期,如訂單會透過不同狀態表示其位於生命週期的某一階段,而且單一訂單會有一個單一訂單編號去區隔不同訂單,因此可以理解訂單為一個實體。值對象的範例,如訂單裡的收據就是一種值對象,收據對象一旦初始化後就不可背改變,即使收據本身也會有收據號碼做為單一識別,但收據本身不存在生物件的命週期或是狀態改變這類的行為(邏輯),一旦收據產出會就具備不變性,因此收據就屬於值對象的範圍。 ```java public interface Identified { //實體-判斷重複的方式 static boolean isDuplicated(Collection<? extends Identified> collection) { if (isEmpty(collection)) { return false; } long count = collection.stream().map(Identified::getIdentifier).distinct().count(); return count != collection.size(); } //實體-取得物件識別子 String getIdentifier(); } // 實體 - 聚合根 public abstract AggregateRoot implements Identified{ //實體-件識別子 private String id //實體-物件狀態 private String status; //...略 public AggregateRoot(String id){ this.id = id; this.status = "NEW"; } @Override public String getIdentifier() { return id; } } //實體-訂單 繼承 聚合根 public class Order extends AggregateRoot{ String orderId; //值對象 - 貨物Product Product[] products; //值對象 - 收據Receipt Receipt receipt; //...略 public Order(){ super(_newOrderId()); ... } //newOrderId方法,需設定orderId以及回傳orderId private String _newOrderId(){ this.id = UUID.randomUUID().toString(); return this.id; } } @Getter public class Receipt{ private String reciptId; private String custId; private String custName; public Receipt(String reciptId, String custId, String custName){ this.reciptId = reciptId; this.custId = custId; this.custName = custName; //一旦收據被建立後就不會被改變,遵從"不變性" } } ``` 上述案例演示了實體/聚合根/值對象在程式裡大概的樣貌,但不是唯一標準。如果稍微深入研究DDD就會發現,DDD是一個方法論,其實類似於OOP或是設計模式,但DDD對於細節的要求更高更嚴謹,也因此會導致進入門檻較高或是程式設計上會特別設計符合DDD原則的特例物件這樣的問題,有的時候程式設計師應該還是要把控,不應該為了用而用。 * 領域模型 - 工廠 在DDD的理念裡,聚合根這類實體(物件)的創建過程應該也同屬業務邏輯之內,為了可以體現業務邏輯以及為了遵守關注點分離的原則,建構物件的過程應該要被封裝到工廠中,在實作上可以是獨立的工廠類別或是透過物件的工廠方法皆可,當然也可以參考設計模式的工廠模式結合實作完成工廠的設計。 * 領域模型 - 領域事件 在領域模型,通常一個業務操作只會導致一個結果,這個結果就是所謂的領域事件,可以簡單理解為領域中已經發生或是已經完成的業務操作或是步驟,例如更新成員手機這個功能在更新完成後就會產出一個"領域事件"-成員手機已更新,若後續有其他模組有相對應的後續動作也會等到收到領域事件後開始進行,這裡的模組可以泛指聚合根、其他外部系統或是其他業務模組。更細部的領域事件,我們可以留到後面章節再一起討論。 * 戰術設計 - 資源庫(Repository) 資源庫理解上可以是聚合根取用以及儲存的管道,概念類似於ORM的DAO,但不同的是資源庫取用的基本單位是聚合根,是屬於充血物件的範疇。 * 戰術設計 - 應用服務 領域模型主要的功能為完成業務,當然也負責回應用戶發起的各種請求(Request),但在系統架構上在請求到達領域模型前;還需要處裡些其他事務,例如從資料庫加載聚合根、處裡事務(Transactional)工作,認證或是權限控制等,在DDD的設計裡,這些工作都是屬於應用服務(Application Service)的範圍。更簡化來說,應用服務可以視為領域模型的門面(FACADE),做類似編排/協調領域模型的角色。 ### 架構分層(Layers)  從架構上看來可能你會發現Repository存在的位置很奇怪,甚至在DDD概念裡會同時位於領域層、應用層跟基礎層三層之間,根據Martin Fowler 的《Patterns of Enterprise Application Architecture》在書中描述的Repository 應該是 "Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects",簡單來說大概就是Repository 的介面屬於領域層,而實做則屬於實體層的工作,但事實上是為了編排領域模型甚至是要跟外部溝通,很多時候應用服務也是需要依賴於Repository的介面的。 * Repository 模式 關於DDD 對於Repository的描述可以參考我的[譯文](https://hackmd.io/Zc3O1R4lRn-zaYlA5F05CA)。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up