当前位置: 首页 > news >正文

CQRS的两种设计方式

明整理:CQRS 到底有哪几种常见设计方式,以及在实际项目中该如何选择。

版本 1:Event Sourcing + CQRS(最常见的讨论版本)

上面这张图是最常见的 Event Sourcing + CQRS 架构,流程大致如下:

  1. Client 发送 Command 请求到 Command Service。
  2. Command Service 执行 Aggregate(聚合根)业务逻辑,产生 Domain Event,并写入 Event Store。
  3. 同时将 Domain Event 发布到 Event Bus。
  4. Query Service 订阅 Event Bus 并接收 Domain Event。
  5. Query Service 将变更同步到查询侧数据库(Read Model DB)。

下面先看 Event Sourcing(ES)的优缺点。

最开始只有 Event Sourcing

Event Sourcing 严格来说是在 DDD 圈子里被大规模推广的,其核心概念是:

数据库只存 Domain Event,而不是传统的 Domain State(领域对象当前状态)。

因为在 DDD 中,Aggregate 的变化会产生 Domain Event,所以理论上只保留事件流也可以重建最终状态。

这样做的好处通常有:

  1. 有所有事件的存档,意味着可以恢复到任一时间点下的状态。
  2. 因为有整个状态的变更历史记录,有利于排查问题。
  3. 一定程度上减少状态与事件“双写不一致”问题。若采用状态存储,需要确保状态持久化与事件发布在同一事务边界内;而 Event Sourcing 天然以事件为事实来源。
  4. 可以降低并发写冲突复杂度。事件通常是追加写,不是原地更新(但仍需要处理 duplicate event 和幂等)。

PS:第 3 点在非 Event Sourcing 架构里也可以通过 transactional outbox(发件箱模式)来解决。

为什么这里会出现 CQRS?

因为每次查询都从事件流重放来恢复状态,读取成本高,所以才需要 CQRS。

在大多数业务系统里,Query 次数远大于 Command。纯 ES 的“写优化”并不能直接满足查询性能诉求,因此通常要引入读模型(Read Model),也就是 CQRS 中的 Query 模式。

这个版本有什么问题?

  • Write Service 不直接保存对象最终状态,某些“先读再写”的业务判断会变复杂,且会遇到读写延迟。
  • 存储与运维成本较高(事件量膨胀、回放成本、快照策略、归档策略等)。
  • 事件模型演进困难:当事件结构变化时,历史事件兼容、重放逻辑、Query 侧投影都要一起考虑。
  • 数据同步是最终一致性,不是强一致性。
  • 事务边界与失败补偿设计复杂(发布失败、重复消费、顺序保证、幂等处理等)。

版本 2:读写分离(更常见的工程化 CQRS)

我认为这才是 CQRS 在工程实践中的主流形态:将模型拆分为两套职责。

  • Write:用 DDD 处理业务规则、状态变更与持久化。
  • Query:围绕 UI 与报表需求,做高性能、可定制查询。

Write 与 Query 分开的原因在于:

Aggregate Model 并不天然适合查询场景:查询成本高,且字段结构不一定匹配前端展示需求。

这种设计方式的常见好处:

  • 职责边界清晰:哪些是 Command、哪些是 Query 一目了然,能显著减少在查询流程里“顺手改数据”的问题。
  • 模型解耦:不会为了查询便利去污染 Aggregate 模型,符合单一职责原则。
  • 支持物理隔离与异构存储:例如写入 MySQL、同步到 PostgreSQL 或 Elasticsearch 做查询。

我在项目里通常采用这种方式。很多开发者也有类似实践,例如 Amichai Mantinband 在视频 Understand Clean Architecture in 7 Minutes 中提到的设计,本质上也是在 Application 层做命令与查询分离。

小结

实际项目中,大多数团队会优先落地“读写分离版 CQRS”,而不是“Event Sourcing + CQRS 全家桶”。

Event Sourcing + CQRS 并非不能用,但它更适合这些场景:

  • 对审计追踪、可回放、时点恢复有强需求。
http://www.cnnetsun.cn/news/3052496.html

相关文章:

  • 基于SpringBoot的学生选课系统设计与实现
  • 计算机毕业设计之电影购票推荐网站的设计与实现
  • YOLO轻量化与部署优化- 第80篇:模型压缩与部署的综合优化指南
  • 记一次 .NET 某注塑模具系统 CPU爆高分析
  • Docker--认识Docker网络
  • FMEA×控制计划×PPAP自动联动,这才是研发管理的天花板-全星研发项目管理APQP软件系统#APQP #PLM #汽车电子 #芯片研发 #新能源 #项目管理软件
  • MSP430 ADC模块深度解析:从寄存器配置到低功耗设计实战
  • | LiveMoments 用参考图引导的扩散模型提升重选封面帧画质
  • 如何通过4个维度全面提升Windows系统性能?
  • 从TPA3111D1评估板到自主设计:D类音频功放实战指南
  • 百度网盘秒传转存终极指南:3分钟掌握全平台快速分享技巧
  • 基于Feign+Resilience4j的微服务熔断防雪崩优化方案
  • 为什么92%的ChatGPT Plus订阅在第3个月自动降级?国内用户必须知道的OpenAI账户健康度监测协议(含自动续费预警脚本开源)
  • 如何在 Python 项目中避免循环引用
  • Win11Debloat:让Windows 11重获新生的终极优化工具
  • 2026 Q1企业级大模型运营治理平台实测排行|合规效率双维度深度对比
  • MATLAB | MATLAB 也可以画 Mantel test 相关性热图了?
  • 深耕綦江十六年:綦江万汇家居建材如何成为本土家居服务商的标杆
  • ⚡SimpleDAO 企业实战教程(06) mergeParams 多组条件合并
  • 卫星合成孔径雷达技术解析 穿透云雨雾霾实现全天时对地探测
  • DRV10964评估板实战:BLDC电机驱动硬件拆解、配置与调优指南
  • 靠《堡垒之夜》游戏录像训练AI,General Intuition获3.2亿美元融资!
  • Conda 环境一键搬家:用 conda-pack 打包带走,连网都不用
  • 现在学习SEO还来得及吗?
  • 重要的桥接Python库
  • ChatGPT Plus 支付失败后,为什么不建议连续重试?
  • 告别论文熬夜卡文!Okbiye 毕业论文 AI 写作工作台全拆解,一站式适配全学段学术创作
  • 企业级 AI 工具选购指南:ChatGPT Team vs Claude Team vs Gemini Business
  • 我来发一个做股票从没亏过的指标成功率
  • GPT-4.1 Nano 轻量化智能应用落地指南