CQRS架构下删除客户操作的前置影响校验与操作阻断实现
场景本质拆解
这套设计矛盾的核心根源,是把「操作前置影响核算」的场景,错误归到了异步事件驱动的写执行流范畴里。
*实际上这个环节从根上就不属于写流程:它没有产生任何领域状态变更,只是需要在用户点击操作、尚未提交正式删除命令的间隙,收集所有关联域的现存关联数据,用于生成确认弹窗供用户选择继续或中止。这类纯查询、纯展示的逻辑,本来就归CQRS架构的读侧管辖,根本不需要套用写侧的异步事件管道规则,自然不存在“异步流程无法同步等待结果”的冲突。
行业通用落地实现方案
- 严格遵循现有域依赖规则设计查询链路
按照当前的域依赖边界(Customer域不依赖任何外部域、Sales域持有客户关联索引、Discussion域持有客户关联索引),不需要打破依赖方向做反向调用:所有持有客户关联关系的域,统一暴露一个只读的内部校验接口,入参为目标实体类型+目标实体ID,出参为当前域下删除该客户会触达的影响条目。
具体到CRM场景的落地:- Sales域对外暴露统一查询接口,接收CustomerID作为入参,返回对应影响内容:“将解除N个销售与该客户的归属绑定关系”
- Discussion域对外暴露同规则的查询接口,接收CustomerID作为入参,返回对应影响内容:“将联动删除N条与该客户关联的历史沟通记录”
前端在用户点击「删除客户」按钮的瞬间,并行调用两个域的只读接口,设置2秒的最长超时阈值,所有接口返回后(或达到超时时间),将收集到的影响条目渲染成确认弹窗。用户选择确认后,再正式向Customer域提交DeleteCustomer的写命令,进入原有的异步级联删除流程即可。
- 彻底避免把前置校验逻辑嵌入写侧事件管道
很多人会踩的设计坑是:把删除命令发出去之后,在异步事件链里等各个关联域返回影响范围事件,再收集结果弹确认框。这种做法的问题非常明显:- 把纯只读的查询逻辑嵌进了写执行流,拉长了写操作的响应链路
- 为了收集跨域的异步事件,需要额外维护临时的等待状态,引入不必要的分布式事务复杂度
- 部分关联域超时故障时,会直接卡死整个删除命令的执行流程
- 一致性兜底规则适配业务诉求
跨域的关联数据本身就是最终一致的,根本不需要追求前置影响核算的100%精准:
因为原有异步级联删除流程本来就会自动处理所有关联域的删除动作,前置核算的核心作用只是给用户提供足够的决策参考,漏算的关联数据完全可以由后台流程兜底执行,等所有域的删除动作完成后,再给用户发送最终的执行结果通知即可。如果查询过程中某域超时不可用,直接在弹窗中提示“当前部分关联系统暂时无法访问,确认删除后系统将自动同步处理可访问范围内的关联数据”即可,不需要为了百分百准确的核算结果阻塞用户操作。
常见设计误区澄清
很多从业者会混淆Saga流程的适用场景,觉得可以用Saga做前置影响核算,这属于典型的杀鸡用牛刀:Saga的设计目标是保障写流程的分布式事务最终一致性,如果你为了做个弹窗的提示信息,专门为删除流程开一条Saga链路——第一步收集所有域的关联数据、第二步等用户确认、第三步执行级联删除——不仅要额外维护Saga的状态存储,还会让本来简单的查询动作背负上写事务的复杂度,算下来的维护成本远大于直接并行调几个只读接口的方案,完全没有必要。
CQRS的核心设计从来不是“所有跨域交互都必须走异步事件”,而是把“负责写状态变更的命令侧”和“负责查询展示的读侧”做边界拆分:命令侧可以全异步通过事件管道做跨域状态同步,读侧则可以根据业务需要任意组合同步查询、并行聚合的逻辑,根本不需要被异步事件的框架限制。
内容的提问来源于stack exchange,提问作者Richard Bladh
相关产品推荐
相关产品推荐

