DDD聚合删除操作:仅pending状态订单可删的规则应放领域层还是应用层?
订单聚合删除规则的DDD分层设计方案
先厘清两个方案的核心问题
两种待评估方案的别扭感,本质都来源于把「流程编排逻辑」和「纯业务不变式」混为一谈,模糊了领域层和应用层的核心边界:
- 领域层的核心职责是承载不可违背的业务不变式,所有逻辑必须围绕聚合自身的状态一致性展开,不包含跨组件调用、流程串接这类流程性工作
- 应用层的核心职责是做全链路流程编排、跨组件协调、事务边界控制,绝对不能承载属于领域核心的业务校验规则,否则一旦新增操作入口就容易漏写规则,造成一致性问题
两个错误实现的具体问题拆解
针对领域层的错误实现:把包含查找订单、校验状态、执行删除全流程的逻辑封装为领域服务
这种做法本质是越界。领域服务的定位是处理「单个聚合边界内无法承载、需要跨多个聚合协作的纯领域逻辑」,从始至终都不应该负责调用仓储查找对象、串接从取数到落库的全链路流程。这种裹着全流程的“领域服务”,本质就是把应用用例换了个名字塞进领域层,直接破坏了DDD的分层依赖方向。
针对应用层的错误实现:直接把状态校验规则写在应用用例里
这种做法的不合理性非常明显:“仅pending状态的订单可删除”是和Order聚合生命周期强绑定的核心业务规则,如果把校验逻辑挪到应用层,后续新增的删除入口(比如定时清理超时pending订单的批处理任务、运营后台的批量删除操作)很容易漏写校验,最终打破业务规则的一致性。
正确的分层落地实现
完全不需要为了这个规则单独写领域服务,把逻辑拆到对应层就行:
领域层:把规则内化到Order聚合根自身
给Order聚合根新增一个和删除动作绑定的纯领域方法,所有校验完全基于聚合自身持有的状态完成,不依赖任何外部组件:
// Order聚合根内部方法 public void MarkOrderForDeletion() { // 纯业务不变式校验,完全由聚合自身的状态决定 if (this.Status != OrderStatus.Pending) { throw new DomainException("仅状态为pending的订单可执行删除操作"); } // 同步更新聚合内部的状态标记,比如设置软删除标识、清理关联的临时值对象状态 this.IsDeleted = true; }
这种实现的好处是:不管后续从哪个入口触发订单删除,只要调用这个方法,就天然带上了状态校验,根本不会出现规则被遗漏的问题,也完全符合聚合根负责维护自身状态一致性的设计原则。
应用层:只做流程编排,不碰业务规则
对应的应用用例只需要按顺序串接流程,全程不写任何关于订单状态的判断逻辑:
- 接收入参,做基础的格式校验(比如校验订单ID是否合法)
- 调用订单仓储,根据ID查出对应的Order聚合实例
- 调用聚合根的
MarkOrderForDeletion()方法,完成状态校验和内部状态标记 - 调用仓储,把变更后的Order聚合持久化落库(软删除/硬删除的最终动作由仓储根据聚合的标记状态执行)
- 控制事务边界,向调用方返回处理结果
常见认知误区澄清
- 不要觉得只要涉及校验就要写领域服务:这个场景的规则完全在单个Order聚合的边界内,根本不需要跨聚合协作,连领域服务都不需要额外编写
- 不要把删除动作的校验下沉到仓储:仓储只负责做对象的持久化存取,绝对不能承载业务校验逻辑,不能拿到订单就直接执行删除
- 不要觉得聚合根只能处理“业务流转动作”:和取消订单、开启订单处理这类状态流转一样,标记待删除状态本身就是聚合生命周期的一部分,完全属于领域层的职责范围
内容的提问来源于stack exchange,提问作者Oleg Khamov
相关产品推荐
相关产品推荐

