Skip to content

聚合 4 规则 = 一致性边界的工程实现 ​

一、业务问题 ​

聚合是 DDD 战术设计中最难也最重要的模式。Vernon 在 Ch 10 给出 4 条核心规则:

  1. 一致性边界就是事务边界
  2. 优先设计小聚合
  3. 跨聚合用 ID 引用
  4. 聚合外用最终一致性

问题:工程团队做反的比例极高——要么做太大,要么做太小。

二、控制论视角 ​

聚合的 4 规则 = 控制系统"作用域"的工程实现。

规则 1(小聚合)= 一个控制器的管辖范围小(强对位 Ch 6 §6.7)
规则 2(ID 引用)= 跨子系统通信用 ID 不用对象(强对位 Ch 21 大系统级通信)
规则 3(最终一致)= 跨子系统异步协调(强对位 Ch 21 分散控制)
规则 4(不变量)= 控制律守护的不变约束(强对位 Ch 4 §4.6 反馈决定稳定)

钱学森 Ch 21 大系统理论:

"大系统的特点:不同的子系统有不同的目标……子系统间需要协调机制。"

软件对位:跨聚合 = 跨子系统;ID 引用 = 子系统间只通过 ID 通信(不深入访问对方);最终一致 = 异步协调(不等待)。

钱学森 Ch 4 §4.6:

"没有反馈,就没有自动控制"——聚合内的"反馈"是事务原子性。

钱学森 Ch 6 §6.7:

"受控对象中的相互关联并不总是有害的……有些耦合作用是有益的。"

软件对位:聚合内的"耦合"是有益的(一起保持一致);跨聚合的"耦合"应最小化。

三、4 条核心规则 ​

规则 1:小而专(Small Aggregates) ​

原则:
  - 优先设计小聚合
  - 只包含真正需要一起保持一致的对象
  - 不需要的对象放外面
  
原因:
  - 大聚合 = 大事务 = 性能差
  - 大聚合 = 锁竞争严重
  - 大聚合 = 难以演化
  
判断"小":
  - 1 个聚合根(必选)
  - 0-3 个内部实体(最好)
  - 0-N 个值对象(无限制)
  - 整个聚合能在 1 个屏幕展示

Vernon 原话:

"Consider the advantages of designing small Aggregates."

规则 2:跨聚合用 ID 引用 ​

原则:
  - 聚合 A 引用聚合 B 时,**只引用 B 的 ID**
  - 不持有 B 的对象引用
  - 不通过 A 修改 B
  
原因:
  - 避免大聚合蔓延
  - 跨聚合用最终一致性
  - 减少耦合

实现:
  # ❌ 跨聚合对象引用
  class Order:
      customer: Customer  # 对象引用

  # ✓ 跨聚合 ID 引用
  class Order:
      customer_id: CustomerId  # ID 引用

Vernon 原话:

"See why you should design Aggregates to reference other Aggregates by identity."

规则 3:聚合外用最终一致性 ​

原则:
  - 聚合内 = 强一致(ACID 事务)
  - 聚合间 = 最终一致(事件 + 异步)
  
原因:
  - 分布式事务性能差
  - 业务可接受短暂不一致
  - 隔离故障

实现:
  [聚合 A 事务] → 发布事件 → 异步处理 → [聚合 B 更新]
  
  例:订单创建后异步发送邮件
  订单聚合内:订单创建(强一致)
  邮件聚合:监听 OrderCreated 事件,异步发送

规则 4:保护业务不变量(Business Invariants) ​

原则:
  - 聚合 = 不变量的守护者
  - 任何时刻不变量必须为真
  - 不变量的破坏 = 业务错误

不变量例子:
  - 订单总金额 = 各商品金额之和
  - 已支付订单不能修改
  - 库存不能为负
  - 聚合内 = 强一致
  - 聚合间 = 最终一致

四、Tell-Don't-Ask & Law of Demeter ​

聚合的两个编码原则:

Tell-Don't-Ask ​

python
# ❌ Ask: 外部查询内部状态再决策
if order.getItems().size() > 0 and order.getStatus() == "NEW":
    order.setStatus("CONFIRMED")

# ✓ Tell: 让 order 自己处理
order.confirm()  # 内部判断 + 修改

Law of Demeter(迪米特法则) ​

原则:只和直接朋友说话
  - 聚合根的方法
  - 根的参数
  - 根自己创建的对象
  - 根的全局变量
  
不要:
  - order.getCustomer().getAddress().getCity()  # ❌ 链式访问

反例:

python
# ❌ 链式访问
customer_name = order.getCustomer().getName()

# ✓ 通过聚合根
customer_name = order.getCustomerName()  # 内部封装

五、聚合的边界判断 ​

关键问题:
  "在一次事务中,哪些对象必须一起保持一致?"
  → 答案决定聚合边界

例:
  "订单创建时,订单项必须在场" → 订单 + 订单项 = 同一聚合
  "支付成功后,订单状态更新" → 订单和支付 = 不同聚合(最终一致)

六、工程实践 ​

实战 1:识别聚合边界 ​

步骤:
  1. 列出业务不变量
  2. 哪些不变量必须在一次事务中保持?
     → 这些对象在同一个聚合
  3. 哪些不变量可异步处理?
     → 这些对象在不同聚合

实战 2:避免大聚合 ​

反模式信号:
  - 一个聚合有 10+ 实体
  - 修改聚合要加载大量对象
  - 多人经常改同一聚合
  - 锁等待严重

修复:
  - 拆聚合
  - 用 ID 引用
  - 跨聚合用事件

实战 3:实现 Tell-Don't-Ask ​

python
# 正例
class Order:
    def __init__(self):
        self.items = []
        self.status = "NEW"
    
    def confirm(self):
        # 业务规则在内部
        if len(self.items) == 0:
            raise BusinessRuleViolation("Cannot confirm empty order")
        if self.status != "NEW":
            raise BusinessRuleViolation("Only NEW orders can be confirmed")
        self.status = "CONFIRMED"
        # 发布领域事件
        self.add_event(OrderConfirmed(self.id))

七、性能指标 ​

指标衡量
平均聚合大小聚合平均包含几个对象(应该 < 5)
跨聚合修改比例多少修改跨聚合(应该高)
聚合冲突率同时改同一聚合的频率(应该低)
不变量覆盖率多少业务规则有不变量守护

八、边界判断 ​

何时用大聚合:

  • 业务有"必须一起改"的强规则
  • 性能不是问题
  • 冲突少

何时用小聚合(绝大多数):

  • 性能要求高
  • 多人协作
  • 业务可接受最终一致

何时跨聚合用同步:

  • 业务绝对不允许不一致(如金融)
  • 用 Saga 模式

何时跨聚合用异步:

  • 业务可接受短暂不一致
  • 性能要求高
  • 故障隔离重要

九、与其他 L 层方法论的关系 ​

方法论关系
aggregate(基础)聚合基础概念
tell-dont-ask-demeter编码原则
cqrs-es跨聚合可用 CQRS 优化
saga跨聚合事务
coordination-deviation跨聚合一致性监控
data-consistency(主题)聚合是核心
dynamic-programming多步聚合规划

十、钱学森原话 ​

Ch 6 §6.7(耦合取舍): "受控对象中的相互关联并不总是有害的……有些耦合作用是有益的。"

Ch 4 §4.6(反馈决定稳定): "没有反馈,就没有自动控制"——聚合内的"反馈"是事务原子性。

十一、总结 ​

聚合 4 规则 = 一致性边界的工程实现:

规则核心
小聚合优先小,避免大聚合
ID 引用跨聚合用 ID,不用对象
最终一致跨聚合用事件
保护不变量聚合 = 不变量守护者

核心原则:

  • 业务不变量决定聚合边界
  • 小聚合 + ID 引用 = 性能 + 可演化
  • 跨聚合用事件(最终一致)
  • Tell-Don't-Ask 让聚合行为内聚

一句话总结:聚合 = 一致性边界,按业务不变量定义,按 4 规则实现。

从名家方法论与工程化思路中蒸馏出自己的工程体系。