DDD实践:创建聚合时如何校验关联聚合的状态?
问题
在同一个限界上下文内存在三个聚合:
PhoneAggregateServiceCenterAggregateServiceWorkAggregate
ServiceWorkAggregate通过ID关联PhoneAggregate和ServiceCenterAggregate。创建新的ServiceWorkAggregate时,前端会发送如下POST请求:
{ "phone_id": "uuid", "servicecenter_id": "uuid" }
不能直接通过这些ID创建聚合,必须执行校验:至少要确认这些UUID是有效的实体ID,还可能需要基于PhoneAggregate和ServiceCenterAggregate的当前状态执行业务逻辑校验。
现有三种思路,需要确定最优实现方式:
- 让
ServiceWorkAggregate的应用服务或领域服务引入三个聚合的仓库。- 子问题:是由应用服务仅传递ID给领域服务,由领域服务加载聚合并完成所有校验(因涉及业务逻辑)?还是由应用服务先获取聚合,再传递给领域服务?
- 将关联聚合视为值对象,在
ServiceWorkAggregate中用值对象Repairable和ServiceLocation替代phone_id和servicecenter_id字段,这两个值对象是对应聚合的不可变副本(从同一张表获取等),此时只需在ServiceWork应用服务中引入一个仓库。 - 是否有更优的实现方式?
最优实现方案分析
思路1的合理性与最佳实践
这是DDD中处理跨聚合校验的常规方案,核心原则是领域逻辑归领域服务,应用服务只做编排:
- 正确的做法是应用服务仅传递ID给领域服务,由领域服务完成校验。原因很简单:业务校验逻辑属于领域规则,必须封装在领域层内,不能泄露到应用层。如果让应用服务先加载聚合再传递,会导致领域逻辑分散,后续规则变更时要同时修改应用层和领域层,违反单一职责。
- 具体流程:应用服务接收请求中的
phone_id和servicecenter_id,调用领域服务的createServiceWork方法并传入这两个ID;领域服务从对应的仓库加载PhoneAggregate和ServiceCenterAggregate,先校验ID有效性(加载失败即无效),再执行业务规则校验(比如该手机是否处于可维修状态、服务中心是否有空闲工位等);校验通过后,领域服务创建ServiceWorkAggregate并返回给应用服务,由应用服务调用仓库保存。 - 注意:领域服务可以是专门的
ServiceWorkDomainService,也可以是ServiceWorkAggregate的静态工厂方法,只要保证校验逻辑在领域层即可。
思路2的局限性
把关联聚合做成值对象副本的方式,只适用于不需要实时校验关联聚合状态的场景:
- 问题在于值对象是不可变副本,无法反映关联聚合的实时状态。比如如果创建服务工单时需要校验手机是否已被注销、服务中心是否已停业,用副本就无法获取最新状态,会导致校验失效。
- 另外,这种方式会增加数据冗余,后续关联聚合的信息变更时,副本无法同步,除非额外做事件同步,反而增加复杂度。所以仅当关联聚合的状态不会影响
ServiceWorkAggregate的创建规则时,才适合用这种方式。
更优的补充方案:基于领域事件的前置校验(可选)
如果系统对性能要求极高,不想每次创建工单都加载两个聚合,可以提前通过领域事件维护一个校验缓存:
- 比如
PhoneAggregate的状态变更时(如从"正常"变为"注销"),发布领域事件,由专门的服务更新缓存中该手机的可维修状态;同理维护服务中心的可用状态缓存。 - 创建工单时,领域服务先查缓存做快速校验,校验通过后再创建聚合,后续可以通过后台异步任务做最终一致性校验(如果缓存和实际数据不一致,触发补偿逻辑)。
- 但这种方案适合高并发场景,引入了最终一致性的复杂度,普通场景下思路1的同步校验已经足够。
最终结论
优先选择思路1的第一种子方案:由应用服务传递ID,领域服务加载关联聚合并完成所有校验。这种方案严格遵循DDD分层原则,保证领域逻辑的内聚性,后续维护和扩展都更清晰。
内容的提问来源于stack exchange,提问作者estw272
相关产品推荐
相关产品推荐

