DDD中如何对Product与Location关联约束场景建模?
DDD 框架下 Product 与 Location 关联场景的落地方案
核心认知校准
该场景下的循环依赖矛盾,本质是对DDD一致性规则的认知偏差:DDD仅要求单个聚合边界内的不变量必须满足强一致性,跨聚合的业务规则不需要强行塞进单个聚合实现,也不需要为了承载规则硬造不存在的上层聚合根,通过领域服务编排、跨仓储查询、领域事件等机制实现即可,完全符合DDD设计规范。
具体建模设计
聚合边界划分
- 将
Product、Location均定义为独立聚合根,二者生命周期完全独立,匹配业务上两类对象可独立创建、删除的规则。 Product聚合内部仅持有Location的全局唯一标识(如locationId),不持有Location的直接对象引用。需求中提到的「Location变更需同步展示到Product信息」属于查询侧需求,不需要在写模型层做对象级关联。- 其余2个与Location业务规则一致的实体,完全复用该聚合设计逻辑,Product聚合中仅持有对应实体的ID引用即可。
核心业务规则实现
1. Product创建前置校验
创建Product的逻辑放在领域服务中编排:
- 接收创建请求后,先通过Location仓储校验传入的
locationId对应Location是否存在、是否有效 - 校验通过后再初始化Product聚合实例,调用Product仓储完成持久化
- 校验不通过直接抛出业务异常,阻断创建流程,保证「Product必须关联有效Location」的规则。
2. Location删除规则落地
仅当无任何Product引用Location时才允许删除,该规则属于跨聚合不变量,不要放在Location聚合根内部实现
根据系统并发量级、性能要求二选一实现即可:
- 强一致实现(适合关联数据量级小的系统):删除Location的逻辑放在领域服务中编排,先调用Product仓储查询是否存在关联该
locationId的有效Product,关联数为0时才执行Location的删除持久化操作,存在关联则直接抛出异常阻断删除。 - 最终一致实现(适合Product数据量级大、关联查询性能差的系统):
- 接收Location删除请求时,先将Location标记为「待删除」状态,新增校验逻辑禁止新的Product关联该状态的Location
- 触发Location删除领域事件,通过异步任务扫描所有关联该
locationId的Product,待关联Product数归0后,再执行Location的真实删除操作 - 若长期存在关联Product导致无法删除,可通过后台管理入口人工介入处理,不会破坏核心业务规则。
3. Location变更同步Product展示
该需求完全在查询层实现,不侵入核心写模型:
- 采用CQRS模式时,直接在读库做Product与Location的表关联,查询时实时拼接最新的Location字段即可
- 若对查询性能要求高,可在Location发生变更时发送领域事件,异步更新Product读模型中冗余的Location展示字段,保证用户看到的信息是最新的。
设计合理性说明
不需要额外抽取上层聚合根承载规则:强行新增上层聚合会把两个独立生命周期的对象绑定到同一个一致性边界下,直接导致聚合粒度过大,创建、修改单个Product或Location时都需要锁住整个大聚合,并发性能极差,也违背了聚合设计「边界最小化、仅保障核心强一致规则」的原则。
常见踩坑规避
- 不要为了查询方便在Product聚合中存储Location的非ID字段,这类冗余展示字段统一放在读模型维护,避免双写不一致问题
- 不要在Location聚合内维护关联的Product ID列表,否则每次Product创建、删除都需要修改Location聚合,会把Location变成超大聚合根,引发严重的写并发冲突
- 不要为了实现校验逻辑在两个聚合间建立双向引用,跨聚合的双向引用是DDD建模明确不推荐的做法,会直接导致领域边界混乱。
内容的提问来源于stack exchange,提问作者Artsiom Miksiuk
相关产品推荐
相关产品推荐

