com um clique
com um clique
在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。
封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。
没有身份标识,通过属性值判断相等的对象。不可变,通常代表领域中的度量或描述。
命令查询责任分离,将数据的写入操作和读取操作分别用不同的模型处理,优化各自的性能。
将DDD战略设计应用于微服务架构,限界上下文指导服务拆分,领域事件实现服务间通信。
对比 MVC 模式与 DDD 方法论的差异,分析 MVC 在复杂业务下的局限性,帮助团队判断何时应采用 DDD。
| name | 聚合 (Aggregate) |
| description | 以聚合根为边界,包含多个相关Entity和ValueObject的集合。保证数据一致性和事务边界。 |
Eric Evans 将聚合定义为:一组相关对象的集合,作为数据变更的整体单元("a cluster of associated objects that we treat as a unit for the purpose of data changes")。每个聚合都有一个聚合根——作为外部访问聚合内部对象的唯一入口,以及一条由根的标识划定的一致性边界。
核心概念:
// 聚合根:Order
public class Order {
private final String orderId;
private List<OrderLine> items; // 从属于聚合
private OrderStatus status;
private Money totalAmount;
public void addItem(Product product, int quantity) {
OrderLine line = new OrderLine(product, quantity);
items.add(line);
recalculateTotal();
}
public void cancel() {
if (!canBeCanceled()) {
throw new IllegalStateException("Cannot cancel order");
}
this.status = OrderStatus.CANCELED;
}
// 只通过聚合根修改数据,不直接访问items
// ❌ 不要:order.getItems().add(...);
// ✅ 要:order.addItem(...);
private void recalculateTotal() {
totalAmount = items.stream()
.map(OrderLine::getTotal)
.reduce(Money.ZERO, Money::add);
}
private boolean canBeCanceled() {
return status == OrderStatus.PENDING || status == OrderStatus.PAID;
}
}
// OrderLine:聚合内的对象,不应该独立存在
public class OrderLine {
private final Product product;
private final int quantity;
public Money getTotal() {
return product.getPrice().multiply(quantity);
}
}
class Order:
def __init__(self, order_id: str, customer_id: str):
self.order_id = order_id
self.customer_id = customer_id
self.items = []
self.status = "PENDING"
self.total_amount = Money(0, "USD")
def add_item(self, product: 'Product', quantity: int):
"""通过聚合根添加项"""
line = OrderLine(product, quantity)
self.items.append(line)
self._recalculate_total()
def can_be_canceled(self) -> bool:
return self.status in ["PENDING", "PAID"]
def cancel(self):
if not self.can_be_canceled():
raise ValueError("Cannot cancel order")
self.status = "CANCELED"
def _recalculate_total(self):
self.total_amount = sum(
(line.get_total() for line in self.items),
Money(0, "USD")
)
class OrderLine:
def __init__(self, product: 'Product', quantity: int):
self.product = product
self.quantity = quantity
def get_total(self) -> 'Money':
return self.product.price.multiply(self.quantity)
// ❌ 错误:从外部直接访问聚合内部
Order order = orderRepository.find("O001");
OrderLine line = order.getItems().get(0); // 直接访问!
line.setQuantity(100); // 修改!
// ✅ 正确:通过聚合根
Order order = orderRepository.find("O001");
order.updateItem(0, 100); // 通过聚合根修改
Vaughn Vernon 在 Implementing Domain-Driven Design(IDDD,红皮书)中提炼了四条被广泛认可的聚合设计经验:
// ✅ 遵循规则:小聚合 + ID 引用
public class Order {
private OrderId id;
private CustomerId customerId; // 通过 ID 引用其他聚合,而非 Customer 对象
private List<OrderLine> items; // 聚合内部实体
private OrderStatus status;
}
// ❌ 违反规则:穿透边界、对象引用
public class Order {
private Customer customer; // 直接持有另一聚合的对象引用
private List<OrderLine> items;
public void changeCustomerEmail(String email) {
customer.setEmail(email); // 跨聚合直接写——破坏单写规则
}
}
"小聚合"是原则,但"多小才算小"没有标准答案。下列实操判据可帮助判断:
判断聚合是否过大:
判断聚合是否过小(被切碎了):
经验判据:
❌ 上帝聚合——把 Order + Customer + Product + Inventory 全塞进一个根 → 用 ID 引用其他聚合,每个聚合只承载自己的真实不变量
❌ 从外部直接操作聚合内部对象——order.getItems().add(...) 跳过聚合根校验
→ 暴露行为方法 order.addItem(...),集合修改入口只在聚合根内部
❌ 跨聚合写在一个事务里——一次事务同时更新 Order、Inventory、Payment → 同一事务只改一个聚合,跨聚合协同用领域事件 + 最终一致性
聚合是建立清晰边界、保证数据一致的关键设计。