DDD多聚合根交互处理咨询:路由应用的Request与Route建模
跨Aggregate逻辑归属与多上下文实体处理
通用规则澄清
- 跨Aggregate的同事务修改需求,不应该直接硬写在Domain Service里拉两个AR操作。Domain Service仅承载不属于任何单一AR的纯领域逻辑,遇到跨AR一致性需求,优先判断边界是否划分错误,实在无法拆到同一AR内的,优先用最终一致性方案,不要默认追求强事务跨AR。
- 不同Bounded Context下的同类实体,本来就应该独立建模,不存在“数据冗余浪费”的问题。每个上下文内的实体只承载当前场景需要的属性和规则,比如职位看板上下文里的职位实体,只需要存储职位ID、展示名称、排序权重即可,不需要携带职位本身的招聘流程、薪资结构等其他上下文的属性,这正是限界上下文的核心设计思路,并非重复开发。
路由业务的落地方案
纠正Aggregate划分的认知误区
之前纠结的高阶Aggregate设计存在概念误解:高阶Aggregate从未要求每次操作都加载全量关联数据,也不要求聚合边界内的所有关联对象都必须随AR一起加载。
判定聚合边界的核心标准非常简单:哪些对象的状态修改,必须保证不存在不一致的中间状态。结合当前业务场景,可以重新调整聚合边界:
- 把Request作为独立AR:它的基础状态流转(待响应、已取消、爽约)只需要自身规则校验即可,不需要等待Route的状态确认,不要把Route下的所有Request都塞进Route的聚合边界。
- 把Route作为独立AR:它自身的状态(待调度、已调度、行驶中、已完成)、内部结构(站点排序、有效载荷关联关系)、外部路由引擎计算结果,都属于Route AR的管控范围。Route AR内部不需要加载每个Request的全量属性,仅存储有效载荷和requestId的关联关系即可,Request的具体状态管控属于Request AR的职责。
- 不要把Routing(外部路由计算结果)作为独立AR:路由结果完全依附于Route存在,没有独立的生命周期和业务规则,Route删除时路由结果同步删除即可,直接作为值对象存在Route的文档结构中,无需单独建集合。
核心用例的逻辑落地
这种边界划分下,完全不需要在单事务中修改多个AR,用领域事件实现最终一致性即可:
- Request未被路由时取消:直接加载Request AR,执行自身取消规则标记状态,不涉及其他AR。
- Request已被路由、Route已调度时取消:
- 加载Request AR,校验状态后执行取消标记,和领域事件写入同一个本地事务提交
- 发布
RequestCancelled事件 - 应用层事件处理器收到事件后,加载对应Route AR,根据requestId找到对应有效载荷,执行移除有效载荷、清理仅关联单个载荷的站点的操作;结构调整完成后调用外部路由引擎重新计算,把新的路由结果更新到Route AR中
- 不追求Request修改和Route修改的强事务一致性,通过事件表的重试机制保证最终一致,业务上可接受的短时间中间态不影响体验。
- Request已被路由、Route行驶中时取消:
- 加载Request AR,标记状态为爽约,同步写入
RequestMarkedAsNoShow事件 - 事件处理器加载对应Route AR,把关联的有效载荷标记为爽约状态,完成Route内部统计调整即可,无需重新调用外部引擎计算。
- 加载Request AR,标记状态为爽约,同步写入
- Route调度完成后启动:
- 加载Route AR,把状态从已调度修改为行驶中,同步写入
RouteStarted事件 - 事件处理器遍历Route下关联的所有requestId,批量加载对应Request AR,把Request状态标记为已匹配,无需为了同步修改Request就把所有Request塞进Route的聚合边界。
- 加载Route AR,把状态从已调度修改为行驶中,同步写入
调整后的Route聚合结构示例
class RouteAggregate { private routeId: string; private status: 'pending' | 'scheduled' | 'in_progress' | 'completed'; private stops: Array<{ stopId: string; sequence: number; payloads: Array<{ payloadId: string; requestId: string; // 仅存ID引用,不加载Request全量实体 status: 'active' | 'cancelled' | 'no_show'; }> }>; private routingResult: RoutingResult; // 依附Route存在的值对象 }
这种结构下,操作单个Request时直接加载Request AR,无需拉取Route全量数据;调整Route结构时直接加载Route AR,无需拉取所有关联Request的全量属性,完全解决之前“无法加载全量高阶AR”的问题。
内容的提问来源于stack exchange,提问作者rage77
相关产品推荐
相关产品推荐

