跳到主要内容
约 5 分钟3 张卡

聚合

聚合是一组由聚合根维护业务规则的对象。本文讲解不变量、事务边界和并发写入。

1. 面试核心​

先记住这几条,再读正文
  • 聚合边界。 聚合按业务不变量划分,由聚合根统一接收修改命令。卡 · 综合题
  • 事务边界。 聚合内的状态变化在同一本地事务中提交。
  • 聚合间引用。 聚合间使用 ID 引用,独立生命周期降低共同修改的成本。
  • 并发更新。 乐观锁用版本条件拒绝旧状态写入,冲突后重新校验业务规则。卡 · 版本冲突与命令重放
  • 实体与值对象。 实体按身份识别,值对象按属性比较,聚合根本身是实体。卡 · 实体与值对象

2. 不变量与聚合​

不变量是业务操作结束后必须成立的规则。例如,订单总额等于有效订单项金额之和。取消后的订单保持取消状态。库存预订量应在可售数量内。

聚合边界取决于这些规则。页面上共同展示的数据可以来自多个聚合。数据库外键也只说明存储关系。

设计时列出命令、涉及的状态和校验规则。必须原子维护的规则由同一边界负责。

3. 聚合根​

聚合根是外部修改聚合的入口。接口表达业务动作:

order.changeQuantity(productId, quantity);
order.confirm(confirmTime);
order.cancel(reason);

订单根检查状态并更新金额。订单项的修改也经由订单根完成。内部对象可以承担计算,根对象控制修改入口。用户鉴权由应用层完成,业务状态校验留在领域模型中。

3.1 实体与值对象​

实体有持续身份,属性变化后仍是同一个对象。订单按订单 ID 识别,状态改变不会创建另一笔订单。

值对象按属性比较,并适合设计为不可变对象。金额由数值与币种共同定义。两份相同金额可以互换,修改时创建新值。

聚合根是拥有身份的实体。聚合内部可以包含其他实体和值对象。仓储按根保存聚合,内部实体无需各自开放写入仓储。

4. 事务边界​

应用服务加载聚合,调用业务方法,再用事务保存。提交前,聚合内的规则必须成立。多表存储时,根对象和子对象共同提交。

一个事务修改一个聚合是设计取向。同一数据库可原子修改多个聚合。若每条命令都需要跨聚合事务,应重新检查边界。真正独立的生命周期可用异步流程协作。

跨服务协调的选择见分布式一致性。

5. 聚合之间的引用​

聚合引用外部对象时保存 ID 或业务快照:

final class Order {
private CustomerId buyerId;
private List<OrderLine> lines;
}

buyerId 标识购买人,订单项属于订单内部。收货地址可保存下单时快照。当前会员等级由应用层查询会员接口。独立引用能减少对象之间的生命周期耦合。

6. 聚合规模与竞争​

聚合过大会增加加载量和写入竞争。无关操作也会争用同一个版本或锁。

例如,客户的全部订单共用一个聚合根。修改两笔不同订单也要争用客户版本。订单有独立生命周期时,应拆成独立聚合。普通只读查询本身并不会争用写锁。

7. 规则分散​

若订单项和总额分别独立提交,失败会留下金额差异。二者需要共同维护同一条金额规则。

边界过小的信号包括重复校验和固定的跨对象修改顺序。判断依据是规则是否需要共同成立。表的数量与聚合数量没有固定比例。

8. 并发控制​

两个请求可同时通过内存中的规则检查。保存时还要检查原先读取的版本:

UPDATE orders
SET status = ?, version = version + 1
WHERE id = ? AND version = ?;

影响 1 行表示版本匹配。影响 0 行表示记录缺失或版本已经变化。调用方重新读取后区分原因。

子对象的修改也要参与根版本检查。根版本更新和子表更新放进同一事务。热点命令可改用行锁或原子条件更新。

8.1 版本冲突与命令重放​

假设两个请求都读取订单版本 7。请求 A 取消订单,提交后版本变成 8。请求 B 确认订单,带版本 7 保存时影响 0 行。

B 应回滚本次事务,并重新加载订单。取消状态会使确认命令校验失败。若只换成版本 8 重发旧对象,就会覆盖合法取消。

可重试命令需要重新计算结果。外部扣款等副作用由业务幂等标识保护。

9. 常见问题​

9.1 聚合与表的映射​

一个聚合可以映射到多张表。仓储按聚合边界保存对象。报表则可以使用专门的读取模型。

9.2 领域事件与提交时机​

10 · 面试题卡片​

10.1 怎样确定聚合边界,聚合过大或过小分别有什么问题​

出现公司:暂无已核验的公开来源(本站自测)

题型:综合题

考察重点

  • 不变量、聚合根与事务边界。
  • 跨聚合协作和最终一致性。
  • 大聚合的争用与小聚合的规则泄漏。

相关内容:不变量与聚合至并发控制。

参考回答

聚合边界由必须原子维护的不变量决定。

订单项和订单总额需要共同保存。聚合根提供修改入口,事务保证整体提交。跨聚合引用使用 ID 或必要快照。

过大的聚合增加加载量和版本竞争。过小的聚合让同一规则分散到多个提交步骤。独立生命周期有助于识别拆分点。并发写入还需版本条件或锁保护。跨聚合流程按实际一致性要求选择协调方式。

10.2 两个请求通过聚合校验后,版本冲突应该怎样处理?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:根版本、事务回滚与命令重放。

相关内容:版本冲突与命令重放。

参考回答

冲突请求应回滚,并在新状态上重新执行命令。

版本 7 的确认请求遇到版本 8 的取消结果后,应按取消规则拒绝。只替换版本号会覆盖合法状态。根与子对象共同提交,才能保护整个聚合。

10.3 实体、值对象和聚合根有什么区别?​

出现公司:暂无已核验的公开来源(本站自测)

考察重点:身份、值相等与聚合修改入口。

相关内容:实体与值对象。

参考回答

实体按身份识别,值对象按属性比较。

订单改状态后仍由原订单 ID 标识。金额则由数值和币种共同定义。聚合根是实体,同时负责聚合的修改入口和规则校验。聚合内可同时包含实体与值对象。

11. 参考资料​