CQRS中命令与查询业务逻辑重复的必要性及权衡处理问询
在CQRS架构中处理命令与查询的业务逻辑重复问题
在CQRS架构里,命令(比如删除项目)和查询(比如返回项目可删除状态)确实经常会碰到需要复用业务逻辑的场景,你提到的DELETE /projects/{id}校验删除规则、GET /projects/{id}返回isDeletable标识就是典型例子。下面是几种权衡思路,帮你根据场景选择合适的方案:
1. 优先提取共享业务规则库
最直接且低成本的解决方式是把判断项目是否可删除的核心逻辑抽成独立的业务规则组件,命令端和查询端都调用这个组件做判断。比如:
- 封装一个
ProjectDeletionRules类,里面写isDeletable(ProjectState state)方法,把「项目处于非活跃状态、无已分配用户、无关联支付选项」这些规则集中实现。 - 命令端执行删除前,调用这个方法校验权限;查询端返回
isDeletable标识时,同样调用这个方法计算结果。
这种方案的优势很明显:
- 完全避免重复代码,规则只维护一次,修改时两边自动同步生效
- 从根源上保证逻辑一致性,不会出现命令端允许删除但查询端显示不可删除的矛盾
- 几乎没有额外开销,只是多了一个纯逻辑的工具类,不需要事件跟踪或额外存储
需要注意的是:这个共享组件必须是无状态、纯函数式的,只接收判断所需的必要参数(比如项目状态、关联用户数、支付选项数量等),不能依赖写模型或读模型的特定存储结构,避免耦合。
2. 仅在性能敏感场景维护isDeletable标识
如果判断规则异常复杂,或者需要跨多个聚合/数据源查询大量数据(比如还要检查关联订单、工单的状态),每次查询都实时计算会严重拖慢响应速度,这时候才考虑在写模型中维护isDeletable标识:
- 写模型在处理所有影响删除规则的事件时(比如用户分配/移除、支付选项关联/解除、项目状态变更),实时计算并更新
isDeletable字段 - 事件发布后,读模型同步这个字段,查询时直接返回即可
但这种方案的代价也不小:
- 写模型的复杂度上升,需要监听所有相关事件并处理状态更新,容易遗漏场景
- 要额外维护
isDeletable字段的存储,增加了数据冗余 - 一旦事件处理出现问题,会导致标识和实际规则不一致,排查成本高
所以这种方案只适合性能瓶颈明确、规则计算成本极高的场景,不要为了避免少量重复逻辑就过早引入。
3. 折中方案:查询端缓存计算结果
如果查询频率很高,但规则计算成本不算特别高,可以在查询端做缓存优化:
- 查询时实时调用共享规则组件计算
isDeletable,然后把结果缓存一段时间(比如5分钟) - 当写模型发生影响删除规则的变更时(比如给项目分配了用户),主动失效对应的缓存
这种方式平衡了一致性和性能,既保留了共享规则库的低维护成本,又能缓解查询压力,适合大多数普通场景。
总结
- 绝大多数场景下,提取共享业务规则库是最优解,简单直接且长期维护成本低
- 只有当规则计算成本极高、查询性能成为核心瓶颈时,才考虑在写模型中维护
isDeletable标识 - 缓存是中间态的折中方案,适合查询频率高但规则复杂度一般的情况
内容的提问来源于stack exchange,提问作者Stefan Milivojevic
相关产品推荐
相关产品推荐

