DDD | 04-什么是聚合根
三、什么是聚合根?
聚合根(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);
}
}