參考來源: 使用DDD指導業務設計的一點思考 https://insights.thoughtworks.cn/ddd-business-design/ 后端開發實踐系列——領域驅動設計(DDD)編碼實踐 https://insights.thoughtworks.cn/backend-development-ddd
領域驅動設計指導業務設計
領域驅動設計是什么
領域驅動設計是 Eric Evans 提出的一種軟件設計方法和思想,主要解決業務系統的設計和建模。
理論發展過程
DDD(Domain-Driven Design,領域驅動設計),對應中文版為《領域驅動設計》。
Implement Domain-Driven Design,簡稱IDDD,中文版《實現領域驅動設計》。 3.《領域驅動設計模式、原理與實踐》問世,簡稱PPPDDD。
IDDD的精華版DDDD(Domain-Driven Design Distilled),《領域驅動設計精粹》。
模型和領域模型
模型是能夠表達系統業務邏輯和狀態的對象。 模型,用來反映事物某部分特征的物件,無論是實物還是虛擬的。 領域,指的特定行業或者場景下的業務邏輯。 DDD 中的模型是指反應 IT 系統的業務邏輯和狀態的對象,是從具體業務(領域)中提取出來的,因此又叫做領域模型。
我們可以吧系統復雜的問題分為兩類:
業務復雜度
技術復雜度 技術復雜度,軟件設計中和技術實現相關的問題,例如處理用戶輸入,持久化模型,處理網絡通信等。 業務復雜度,軟件設計中和業務邏輯相關的問題,例如為訂單添加商品,需要計算訂單總價,應用折扣規則等。
識別上下文的邊界是 DDD 中最難得一部分,同時上下文邊界是由業務變化動態變化的,我們把識別出邊界的上下文叫做限界上下文(Bounded Context)。 使用上下文之后,帶來另外一個收獲。模型之間本質上沒有多對多關系,如果有,說明存在一個隱含的成員關系,這個關系沒有被充分的分析出來,對后期的開發會造成非常大的困擾。
我們將那相關性極強的領域模型放到一起考慮,數據的一致性必須解決,同時生命周期也需要保持同步,我們把這個集合叫做聚合。
聚合中需要選擇一個代表負責和全局通信,類似于一個部門的接口人,這樣就能確保數據保持一致。我們把這個模型叫做聚合根。
相對于非聚合根的模型,我們叫做實體。
我們把沒有自己生命周期的模型,僅用來呈現多個字段的值的模型和對象,稱作為值對象。
使用領域模型指導程序設計
指導數據庫設計
使用領域模型建立數據庫的要點:
留意多對多關系,并拆解成一對多關系
值對象和實體往往為一對一關系
使用 ORM 時,聚合根和實體可以配置為級聯刪除和更新
禁止聚合根之間進行關聯
指導 API 設計
RESTful API 已經變成了主流 API 設計方式,當設計好領域對象后,設計 API 的難度大大降低。
使用聚合根作為 URI 的根路徑,使用實體作為子路徑。通過 ID 作為 Path 參數。指導對象設計
領域模型解決業務復雜度的問題,領域模型只應該被用作處理業務邏輯,存儲、業務表現都應該和領域模型無關。 可以把這些 Plain Object 分為三類:
DTO,和交互相關或者和后端、第三方服務對接
Entity,數據庫表映射
Model,領域模型
指導代碼組織
代碼組織,通俗來說就是如何分包。 DDD 特別的抽離出一層叫做 application。這一層是 DDD 的精華,領域模型關心業務邏輯,但是不關心業務場景。
application 用來隔離業務場景,顯得非常重要。領域驅動設計(DDD)編碼實踐
實現業務的3種常見方式
基于“Service + 貧血模型”的實現 這種方式當前被很多軟件項目所采用,主要的特點是:存在一個貧血的“領域對象”,業務邏輯通過一個Service類實現,然后通過setter方法更新領域對象,最后通過DAO(多數情況下可能使用諸如Hibernate之類的ORM框架)保存到數據庫中。 這種方式依然是一種面向過程的編程范式,違背了最基本的OO原則。另外的問題在于職責劃分模糊不清,使本應該內聚在Order中的業務邏輯泄露到了其他地方(OrderService),導致Order成為一個只是充當數據容器的貧血模型(Anemic Model),而非真正意義上的領域模型。在項目持續演進的過程中,這些業務邏輯會分散在不同的Service類中,最終的結果是代碼變得越來越難以理解進而逐漸喪失擴展能力。
基于事務腳本的實現 我們會發現領域對象(Order)存在的唯一目的其實是為了讓ORM這樣的工具能夠一次性地持久化,在不使用ORM的情況下,領域對象甚至都沒有必要存在。于是,此時的代碼實現便退化成了事務腳本(Transaction Script),也就是直接將Service類中計算出的結果直接保存到數據庫(或者有時都沒有Service類,直接通過SQL實現業務邏輯)。 在系統足夠簡單的情況下完全可以采用。但是:一方面“簡單”這個度其實并不容易把握;另一方面軟件系統通常會在不斷的演進中加入更多的功能,使得原本簡單的代碼逐漸變得復雜。
基于領域對象的實現 在這種方式中,核心的業務邏輯被內聚在行為飽滿的領域對象(Order)中,實現Order類如下:
public void changeProductCount(ProductId productId, int count) {
if (this.status == PAID) {
throw new OrderCannotBeModifiedException(this.id);
}
OrderItem orderItem = retrieveItem(productId);
orderItem.updateCount(count);
}
然后在Controller或者Service中調用:
@PostMapping("/order/{id}/products")
public void changeProductCount(@PathVariable(name = "id") String id, @RequestBody @Valid ChangeProductCountCommand command) {
Order order = DAO.byId(orderId(id));
order.changeProductCount(ProductId.productId(command.getProductId()), command.getCount());
order.updateTotalPrice();
DAO.saveOrUpdate(order);
}
可以看到,所有業務(“檢查Order狀態”、“修改Product數量”以及“更新Order總價”)都被包含在了Order對象中,這些正是Order應該具有的職責。