DDD场景下如何将非领域对象数据传递至事件处理器
DDD场景下如何将非领域对象数据传递至事件处理器
嘿,我来帮你梳理下这个问题,其实你遇到的核心矛盾是觉得操作理由属于过程日志数据,不想污染领域模型,但又需要把它传递到事件处理器里生成日志。咱们一步步拆解解决方案:
首先要纠正一个小误区:操作理由虽然不影响聚合根的状态流转,但它是触发这些领域操作的必要业务上下文——你的业务规则明确要求执行提交/拒绝操作时必须提供理由,所以它完全可以作为领域方法的参数,并被包含在领域事件中,这并不违背DDD的原则,反而能在领域层保证业务约束的执行。
下面是具体的调整方案:
1. 调整聚合根方法,引入操作理由作为参数
把需要理由的领域方法(associate、reject)加上理由参数,同时在方法内部校验理由的合法性(比如非空),确保业务约束在领域层被遵守:
public class ApprovalFlow { // ... 原有字段不变 public void associate(ApproverId approverId, String reason) { // 领域层校验理由非空,保证业务规则 Objects.requireNonNull(reason, "提交理由不能为空"); this.approverId = approverId; // 将理由传入领域事件 eventBus.push(new AssociateEvent(approvalFlowId, approverId, reason)); } public void reject(String rejectReason) { Objects.requireNonNull(rejectReason, "拒绝理由不能为空"); this.status = Status.COMPLETED; this.approvalResult = ApprovalResult.REJECT; // 将拒绝理由传入领域事件 eventBus.push(new RejectEvent(approvalFlowId, rejectReason)); } // ... 原有枚举不变 }
2. 更新领域事件定义,包含理由数据
修改对应的领域事件,新增理由字段,让事件能携带操作时的上下文信息:
public class AssociateEvent { private final Long approvalFlowId; private final ApproverId approverId; private final String reason; // 新增理由字段 public AssociateEvent(Long approvalFlowId, ApproverId approverId, String reason) { this.approvalFlowId = approvalFlowId; this.approverId = approverId; this.reason = reason; } // 对应的getter方法 public String getReason() { return reason; } } public class RejectEvent { private final Long approvalFlowId; private final String rejectReason; // 新增拒绝理由字段 public RejectEvent(Long approvalFlowId, String rejectReason) { this.approvalFlowId = approvalFlowId; this.rejectReason = rejectReason; } // 对应的getter方法 public String getRejectReason() { return rejectReason; } }
3. 调整上层调用链路,传递理由参数
把API层接收的理由参数一路传递到聚合根的方法中,确保上下文不丢失:
// remote api method - 提交给上级场景 public void submitNextApprover(Long approvalFlowId, Long nextApproverId, String reason) { check(approvalFlowId != null, "approvalFlowId cannot be null"); check(nextApproverId != null, "nextApproverId cannot be null"); check(StringUtils.isNotBlank(reason), "reason cannot be blank"); commandService.submitNextApprover(approvalFlowId, nextApproverId, reason); // 新增reason参数 } // command service - 提交给上级场景 public void submitNextApprover(Long approvalFlowId, Long nextApproverId, String reason) { ApprovalFlow approvalFlow = approvalRepository.find(approvalFlowId); approvalFlow.associate(new ApproverId(nextApproverId), reason); // 传递reason到聚合根 approvalRepository.save(approvalFlow); } // remote api method - 拒绝场景 public void rejectApproval(Long approvalFlowId, String rejectReason) { check(approvalFlowId != null, "approvalFlowId cannot be null"); check(StringUtils.isNotBlank(rejectReason), "rejectReason cannot be blank"); commandService.rejectApproval(approvalFlowId, rejectReason); // 新增rejectReason参数 } // command service - 拒绝场景 public void rejectApproval(Long approvalFlowId, String rejectReason) { ApprovalFlow approvalFlow = approvalRepository.find(approvalFlowId); approvalFlow.reject(rejectReason); // 传递rejectReason到聚合根 approvalRepository.save(approvalFlow); }
4. 事件处理器直接从事件中获取理由生成日志
现在事件里已经包含了所需的理由数据,处理器可以直接读取并生成日志:
// 提交给上级的事件处理器 @Subscribe public void handleEvent(AssociateEvent associateEvent) { AssociateLogPo associateLog = new AssociateLogPo(); associateLog.setApprovalFlowId(associateEvent.getApprovalFlowId()); associateLog.setApproverId(associateEvent.getApproverId().getValue()); associateLog.setReason(associateEvent.getReason()); // 直接从事件获取理由 associateLogMapper.insert(associateLog); } // 拒绝操作的事件处理器 @Subscribe public void handleEvent(RejectEvent rejectEvent) { RejectLogPo rejectLog = new RejectLogPo(); rejectLog.setApprovalFlowId(rejectEvent.getApprovalFlowId()); rejectLog.setRejectReason(rejectEvent.getRejectReason()); // 直接从事件获取拒绝理由 rejectLogMapper.insert(rejectLog); }
为什么这个方案符合DDD?
- 没有污染聚合根的持久化状态:理由只是作为操作的临时上下文传入,不会被持久化到
ApprovalFlow实体中,保持了领域模型的纯净性。 - 业务约束在领域层落地:把理由校验放在聚合根方法里,避免了上层代码漏传理由的情况,确保业务规则被严格执行。
- 领域事件完整记录操作上下文:领域事件的核心作用就是记录领域操作发生时的关键信息,把理由包含进去,让事件能完整描述操作的全貌,这完全符合事件驱动的设计思路。
对比你之前的尝试:
- 第一种“找日志再填充”的方法,存在操作和日志无法精准匹配的风险(比如短时间内多个同类型操作),可靠性差;
- 第二种“临时存储再查询”的方法,绕过了领域模型,会导致领域逻辑和外部存储耦合,后续维护成本很高。
这个方案既解决了数据传递的问题,又严格遵循了DDD的设计原则,应该能完美适配你的业务场景。
备注:内容来源于stack exchange,提问作者Jeremy Huang
相关产品推荐
相关产品推荐

