作者:京東物流 趙勇萍
最近有空會跟同事討論DDD架構的實踐落地的情況,但真實情況是,實際中對於領域驅動設計中的實體,值物件,聚合根,領域事件這些戰術類的實踐落地,每個人理解依然因人而異,大概率是因為這些概念還是有一些抽象,同時有有別於傳統的MVC架構開發。
在此,通過小demo的方式跟大家分享一下我對DDD中戰術層級的理解,算是拋磚引玉,該理解僅代表我個人在現階段的一個理解,也可能未來隨著業務經驗深入,還會有不同的理解。
既然說是小demo,還是要從業務場景出發,也就是我最熟知的電商業務場景說起。但是該篇文章裡, 我會簡化一些實際業務場景中的複雜度,通過最小顆粒度的demo,來反映實踐過程中的基本問題。
話不多說,我先丟擲我自己假設的一個業務場景,就是我們熟知的電商網站下單購物的場景。具體細節如下:
• 商品:擁有唯一標識、名稱、價格、庫存等屬性。
• 訂單:擁有唯一標識、下單時間、狀態等屬性。訂單包含多個訂單項。
• 地址:擁有省、市、區、詳細地址等屬性。
• 訂單建立事件:當用戶下單時觸發該事件,包含訂單資訊、商品資訊等資料。
• 訂單支付事件:當用戶完成支付時觸發該事件,包含訂單資訊、支付金額等資料。
• 訂單發貨事件:當商家發貨時觸發該事件,包含訂單資訊、快遞公司、快遞單號等資料。
• 商品聚合根:包含商品實體和相關的值物件,負責商品的建立、修改、查詢等操作。
• 訂單聚合根:包含訂單實體和相關的值物件,負責訂單的建立、修改、查詢等操作。
• 建立訂單介面:使用者提交購買請求後,系統建立相應的訂單,並觸發訂單建立事件。
• 支付訂單介面:使用者完成支付後,系統更新訂單狀態,並觸發訂單支付事件。
• 發貨介面:商家發貨後,系統更新訂單狀態,並觸發訂單發貨事件。
• 查詢訂單介面:使用者可以根據訂單號等條件查詢自己的訂單資訊。
該demo中,商品和訂單是兩個核心領域概念,分別由對應的聚合根負責管理。同時,通過定義領域事件,實現了不同業務場景下的資料更新和通知。最後,對外提供了一組簡單的介面服務,方便系統的使用和擴充套件。
好了,有了以上我們對業務場景的充分剖析,確定了子域,接下來我們該寫我們的程式碼。
// 省略getter/setter方法
public class Product {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
}
2. 訂單實體類
// 省略getter/setter方法
public class Order {
private Long id;
private LocalDateTime createTime;
private Integer status;
private List orderItems;
}
3. 訂單項實體類
// 省略getter/setter方法
public class OrderItem {
private Long id;
private Product product;
private Integer quantity;
private BigDecimal price;
}
4. 地址值物件
// 省略getter/setter方法
public class Address {
private String province;
private String city;
private String district;
private String detail;
}
5. 領域事件類
//訂單建立領域事件
public class OrderCreatedEvent {
private Order order;
private List orderItems;
public OrderCreatedEvent(Order order, List orderItems) {
this.order = order;
this.orderItems = orderItems;
}
}
//訂單支付領域事件
public class OrderPaidEvent {
private Order order;
private BigDecimal amount;
public OrderPaidEvent(Order order, BigDecimal amount) {
this.order = order;
this.amount = amount;
}
}
//訂單
public class OrderShippedEvent {
private Order order;
private String expressCompany;
private String expressNo;
public OrderShippedEvent(Order order, String expressCompany, String expressNo) {
this.order = order;
this.expressCompany = expressCompany;
this.expressNo = expressNo;
}
}
6. 商品聚合根
public class ProductAggregate {
private ProductService productService;
public void createProduct(Product product) {
productService.create(product);
}
public void updateProduct(Product product) {
productService.update(product);
}
public void deleteProduct(Long productId) {
productService.delete(productId);
}
public Product getProductById(Long productId) {
return productService.getById(productId);
}
}
7. 訂單聚合根
public class OrderAggregate {
private OrderService orderService;
public void createOrder(Order order, List orderItems) {
orderService.create(order);
// 觸發訂單建立事件
DomainEventPublisher.publish(new OrderCreatedEvent(order, orderItems));
}
public void payOrder(Long orderId, BigDecimal amount) {
orderService.pay(orderId, amount);
// 觸發訂單支付事件
DomainEventPublisher.publish(new OrderPaidEvent(orderService.getById(orderId), amount));
}
public void shipOrder(Long orderId, String expressCompany, String expressNo) {
orderService.ship(orderId, expressCompany, expressNo);
// 觸發訂單發貨事件
DomainEventPublisher.publish(new OrderShippedEvent(orderService.getById(orderId), expressCompany, expressNo));
}
public Order getOrderById(Long orderId) {
return orderService.getById(orderId);
}
}
通過以上demo,對於實體和值物件,大家會很好理解,並且很直觀。但是, 我額外想重點解釋一下聚合根和領域事件的概念
1. 聚合根
從上面的demo可以看出,在合根類中,我們定義了商品和訂單的增、刪、查等操作,並且為訂單定義了建立訂單、支付訂單、發貨等業務邏輯程式碼。
聚合根是一個物件,它代表一組相關聯的物件的整體。在聚合根內部,可以包含多個實體物件和值物件。聚合根通常可以通過唯一識別符號來進行識別和存取。它是整個聚合的管理者,負責維護聚合之內的一致性,並協調各個實體物件之間的關係。聚合根通常具有豐富的行為和操作,可以對聚合內部的物件進行復雜的操作。
所以說,真正的聚合根內的方法是基於充血模型封裝的,而不是僅僅是對物件的資料封裝。在聚合根中,物件不僅封裝了資料,還包含了相應的行為和業務邏輯。這意味著在一個聚合根中,物件可以自己處理自己的業務邏輯,而不需要外部的控制。就如同demo中所寫的那樣,訂單物件可能包含一些關於訂單處理和交付的方法,如確認訂單、取消訂單、發貨等。
2. 領域事件
領域事件是DDD中最重要的概念之一,他是解決子域之間耦合的重要手段,因為它們提供了一種將領域概念和業務語言轉化為程式碼的方法。當一個領域事件發生時,它會觸發一些操作,這些操作可能會更改系統的狀態,也可能會導致其他領域事件的發生。通過對領域事件進行建模,我們可以更好地瞭解業務過程並設計出更加符合實際需求的系統。
在DDD中,領域事件通常由三個部分組成:
事件名稱:這個名稱應該能夠簡潔明瞭地描述事件所代表的業務意義。
相關資料:這些資料包含了事件發生時與事件相關的所有資訊。例如,在一個電子商務系統中,如果訂單被提交,則訂單資訊以及買家和賣家的資訊都應該包括在該事件中。
傳送者和接收者:傳送者通常是觸發事件的物件,接收者則是事件處理的物件。
領域事件在DDD中有很多用途。例如,它們可以用來觸發其他業務流程、更新資料庫或通知其他子系統。它們還可以用於解決一些複雜的業務邏輯問題,例如並行、資料同步和錯誤處理等等。
總之,領域事件是DDD架構中非常重要的概念,它可以幫助我們更好地理解業務過程,設計出更加符合實際需求的系統,並提高系統的可維護性和可延伸性。