三、什麼是聚合根?
聚合根(Aggregate Root)是DDD中的一個核心概念,用於組織和管理一組相關的領域物件,確保它們的整體一致性和完整性。聚合根是領域模型中的關鍵元件,它不僅封裝了領域內的複雜業務邏輯,還提供了控制訪問和維護資料一致性的機制,是構建可維護、可擴充套件的軟體系統的重要基石。
主要特點
- 聚合的概念:聚合是一組相關物件的集合,這些物件一起形成一個完整的業務概念,並作為一個單元進行操作。在聚合內部,物件之間有嚴格的關聯和依賴關係。
- 根實體:聚合根是聚合中的一個特殊實體,它是外部世界訪問聚合內部其他成員的唯一入口點。這意味著外部物件不能直接持有聚合內非根實體的引用,必須透過根實體來訪問和操作它們。
- 邊界定義:每個聚合都有一個清晰的邊界,這個邊界定義了聚合的範圍,以及哪些操作可以在聚合內部執行,哪些操作需要透過根實體來協調。這有助於維護資料的一致性和完整性。
- 一致性保證:聚合根負責維護其內部的所有業務規則和一致性約束。當外部嘗試修改聚合狀態時,所有必要的驗證和業務邏輯都在聚合根內部執行,確保操作的原子性和一致性。
- 唯一標識:聚合根擁有一個全域性唯一的識別符號(
ID
),外部系統透過這個ID
來識別和訪問聚合。這個ID
也是與其他聚合建立關聯的基礎。 - 生命週期管理:聚合根還負責管理器內部物件的生命週期,包括建立、更新和刪除聚合內的實體和值物件。
設計原則
明確的邊界
- 聚合根定義了一個清晰的邊界,限定那些物件屬於聚合內部,那些屬於外部。邊界內的物件作為一個整體處理,對外部隱藏內部細節
單一入口點
- 聚合根是外界訪問聚合內部其他物件的唯一合法途徑。外部物件不能直接持有或操作聚合內的非根實體,所有互動都需透過根實體的方法進行
內聚性
- 聚合內的所有物件應緊密相關,共同完成一個業務功能。這意味著聚合內的物件和操作應該圍繞著一個核心業務概念組織
一致性規則封裝
- 聚合根負責維護其內部的一致性,包括業務規則、驗證邏輯等。所有改變聚合狀態的操作都應該在根實體中實現,確保操作的原子性和業務規則的遵守
標識唯一性
- 每個聚合根都有一個全域性唯一的識別符號(
ID
), 用以區分不同的聚合例項。這個ID
也用於外部對聚合的引用
生命週期管理
- 聚合根控制其內部成員(實體和值物件)的生命週期,包括它們的建立、更新和刪除
有限的大小
- 為了保持聚合的可管理和易於理解,通常建議聚合的大小不要過大。過大的聚合可能導致效能問題和複雜度增加
聚合內部引用
- 聚合內部的物件可以通常引用相互協作,但這些引用應限制在聚合邊界之內。對合聚合間的關聯,通常適用ID進行間接引用
事務邊界
- 在事務處理中,聚合根常常作為事務的邊界,確保事務內的所有操作要麼全部成功,要麼全部失敗,以此來維護資料的完整性
問題探討
聚合根和實體物件有什麼區別?
身份標識(Identity)
- 實體(Entity):實體具有唯一標識(
ID
),這個ID
用來區分領域中的每一個單獨的實體例項。實體的ID在整個系統範圍內具有唯一性 - 聚合根 (Aggregate Root):聚合根也是一個實體,但它在一個聚合中扮演領導角色。它的ID不僅在系統範圍內是唯一的,而且是外部世界訪問聚合內部其他實體的入口點
聚合邊界(Aggregate Boundary)
- 實體:實體可以是聚合內部的一部分,也可以是獨立存在的。如果實體是聚合內部的一部分,則其ID在聚合內部唯一即可,外部訪問該實體必須透過聚合根
- 聚合根:定義了聚合的邊界,決定了那些物件屬於聚合內部。外部物件不能直接訪問聚合內的非根實體,只能透過聚合根暴露的介面進行操作
職責與控制
- 實體:實體主要負責維護自身的狀態和行為,可能包含一些簡單的業務邏輯
- 聚合根:負責維護整個聚合的一致性,包括內部實體的狀態更改和業務規則的執行。它控制著對聚合內部元素的所有修改,確保在任何時刻聚合都處於有效狀態
生命週期關聯
- 實體:實體的生命週期通常由其所在聚合根或外部服務(如
Repository
)管理 - 聚合根:除了管理自己的生命週期外,還間接管理其內部實體的生命週期,決定它們的建立、更新和刪除
訪問控制
- 實體:如果不是聚合根,實體通常不直接暴露給外部,外部元件不應直接持有實體的引用
- 聚合根:是外部訪問聚合內部的唯一合法通道,提供對外介面,隱藏內部實現細節和複雜性
綜上所述,聚合根是一種特殊的實體,它不僅代表一個獨立的業務概念,還負責協調和保護其內部的其他實體和值物件,確保整體的業務規則得到執行,維持資料的一致性和完整性。實體則是領域模型中的基本構建塊,標識具有唯一標識的領域物件,而聚合根在實體之上提供了一層額外的結構和控制。
你覺得User是一個實體物件還是聚合根?
在大多數情況下,User
通常被設計為一個聚合根,因為它能更好地適應業務需求的變化和複雜性。
- 唯一識別符號:
User
擁有一個全域性唯一的識別符號(如使用者ID),這滿足實體的基本特徵 - 業務操縱的中心:使用者賬戶是許多業務操作的中心,如登陸、資料編輯、許可權管理等。這些操作往往涉及到使用者資訊的修改和驗證,因此需要一個統一的入口點來維護這些操作的一致性和安全性,這正是聚合根的作用
- 關聯管理:使用者可能與其他領域物件關聯,比如使用者可能擁有多個地址、多個角色或者與多個訂單相關聯。作為聚合根,
User
可以管理這些關聯關係,控制對這些關聯物件的訪問和修改,確保資料的完整性和一致性 - 許可權和安全:在很多系統中,使用者資料是非常敏感的,需要嚴格控制訪問許可權。透過
User
設計為聚合根,可以更集中地實施安全策略和訪問控制邏輯
程式碼示例
設計一個訂單聚合類(OrderAggregate)
/**
* 訂單聚合類,封裝了訂單的相關資訊和行為。
* 包括訂單ID、顧客ID、訂單狀態、訂單項、支付狀態等。
*/
public class OrderAggregate {
private final UUID orderId;
private final UUID customerId;
private OrderStatusVO status;
private final List<OrderLineItemEntity> lineItems;
private boolean isPaid;
/**
* 建構函式初始化訂單聚合體。
* @param orderId 訂單ID
* @param customerId 顧客ID
* @param status 訂單初始狀態
*/
public OrderAggregate(UUID orderId, UUID customerId, OrderStatusVO status) {
this.orderId = orderId;
this.customerId = customerId;
this.status = status;
this.lineItems = new ArrayList<>();
this.isPaid = false;
}
/**
* 新增訂單項。
* 如果訂單已支付,則丟擲IllegalStateException異常。
* @param lineItem 要新增的訂單項
*/
public void addLineItem(OrderLineItemEntity lineItem) {
if (!isPaid) {
lineItems.add(lineItem);
} else {
throw new IllegalStateException("Cannot add line item to paid order");
}
}
/**
* 移除訂單項。
* @param lineItem 要移除的訂單項
*/
public void removeLineItem(OrderLineItemEntity lineItem) {
lineItems.remove(lineItem);
}
/**
* 標記訂單為已支付。
* 如果訂單已支付,則丟擲IllegalStateException異常。
*/
public void pay() {
if (!isPaid) {
isPaid = true;
status = OrderStatusVO.PAID;
} else {
throw new IllegalStateException("Order is already paid");
}
}
/**
* 更改訂單狀態。
* @param newStatus 新的訂單狀態
*/
public void changeStatus(OrderStatusVO newStatus) {
this.status = newStatus;
}
/**
* 計算訂單的總金額。
* @return 訂單的總金額
*/
public BigDecimal calculateTotalAmount() {
return lineItems.stream()
.map(OrderLineItemEntity::calculateTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 獲取訂單ID。
* @return 訂單ID
*/
public UUID getOrderId() {
return orderId;
}
/**
* 獲取顧客ID。
* @return 顧客ID
*/
public UUID getCustomerId() {
return customerId;
}
/**
* 獲取訂單狀態。
* @return 訂單狀態
*/
public OrderStatusVO getStatus() {
return status;
}
/**
* 獲取訂單項列表。
* @return 訂單項列表
*/
public List<OrderLineItemEntity> getLineItems() {
return lineItems;
}
/**
* 檢查訂單是否已支付。
* @return 如果訂單已支付返回true,否則返回false
*/
public boolean isPaid() {
return isPaid;
}
/**
* 返回訂單聚合體的字串表示。
* @return 訂單聚合體的字串表示
*/
@Override
public String toString() {
return "OrderAggregate{" +
"orderId=" + orderId +
", customerId=" + customerId +
", status=" + status +
", lineItems=" + lineItems +
", isPaid=" + isPaid +
'}';
}
/**
* 比較兩個訂單聚合體是否相等。
* @param o 要比較的訂單聚合體
* @return 如果兩個訂單聚合體相等返回true,否則返回false
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OrderAggregate that = (OrderAggregate) o;
return isPaid == that.isPaid && Objects.equals(orderId, that.orderId) && Objects.equals(customerId, that.customerId) && status == that.status && Objects.equals(lineItems, that.lineItems);
}
/**
* 計算訂單聚合體的雜湊碼。
* @return 訂單聚合體的雜湊碼
*/
@Override
public int hashCode() {
return Objects.hash(orderId, customerId, status, lineItems, isPaid);
}
}