关于 arch-cqrs
arch-cqrs 是介绍命令查询职责分离的架构技能,说明如何拆分写入模型与查询模型,并帮助团队评估适用场景、投影方式和最终一致性。
主要功能
- 01拆分命令模型与查询模型
- 02通过事件更新读取模型
- 03比较共享或独立存储
- 04评估最终一致性
- 05识别 CQRS 反模式
适用场景
- 01应对读写负载不均
- 02提供仪表盘、搜索和详情视图
- 03将事务写入与查询存储配合
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill arch-cqrs安装后在 Claude Code 中输入/arch-cqrs确认已加载。
详细介绍
一句话总结
将用于命令(改变状态的操作)的模型与用于查询(只读视图)的模型分开。两者可以独立演进、扩展和持久化。
适用场景
- 读负载与写负载严重不对称——每次写入对应许多次查询,或反之。
- 不同的读取场景需要同一份数据的不同形态(仪表板、搜索、详情页)——采用单一规范读取模型会让步求全。
- 自然的写入模型(例如带有丰富不变量的聚合)不便于查询视图。
- 写入端受益于事件溯源,但读取端需要快速的 SQL 查询——CQRS 可以充当桥梁。
不适用场景
- 读写负载大致均衡,且使用相同的数据形态——CQRS 只会增加开销。
- 团队不熟悉最终一致性——CQRS 会有意引入这种机制。
- 系统规模较小,使用常规仓储模式即可满足需求——属于过度设计。
- 用“CQRS”来为同一份数据保留两张数据库表辩护,却没有实际需求驱动——这只是数据重复。
核心内容
两个模型,一个底层事实。
- 写入模型——接收命令(
PlaceOrder、CancelShipment)。进行验证、强制执行不变量并持久化。通常是一个聚合(ddd-aggregate)。针对一致性优化,而非读取。 - 读取模型——接收查询。返回针对特定使用方定制的 DTO。可以采用反规范化、预先联接、缓存或为搜索建立索引。不强制执行不变量——它是一种投影。
同步方式。 写入会生成事件(通常是领域事件;参见 ddd-domain-event)。读取模型是投影——处理程序使用事件并更新物化视图。读取端总是略微落后于写入端。这就是最终一致性;应有意识地接受它。
存储方式。
- 同一数据库,两个模型。 最简单的方式。写入模型和读取模型使用同一数据库中的不同表(甚至不同架构)。读取模型通过触发器、定时任务或进程内投影来填充。
- 不同数据库。 写入事务型存储(聚合使用 Postgres);投影到查询存储(搜索使用 Elasticsearch;热点查询使用 Redis;仪表板使用单独的 Postgres 架构)。会带来实际的运维复杂性,只有确有读取需求时才值得采用。
CQRS 并不要求事件溯源。 两者经常配合使用,但彼此独立。你可以在普通的 CRUD 写入模型上采用 CQRS,并让它在提交时生成事件——若事件本身是事实来源,请参见 arch-event-sourcing。
关于最终一致性的讨论。 读取模型存在延迟。如果领域专家表示“用户期望立即在仪表板上看到自己的更改”,你可以选择缩短投影延迟,或者接受在 UI 中提示“正在处理您的更改”。不要假装这种延迟不存在。
决策启发
- 只有在读写不对称性可衡量,或读写之间的形态存在实质差异时才使用。不要因为“CQRS 听起来很先进”就采用。
- 默认先采用单数据库 CQRS(在一个 Postgres 中使用两个模型)。只有在读取存储的特性(搜索、地理空间、内存存储)与写入存储确实不同时,才使用不同数据库。
- 只有在存在下游事件使用方时才采用事件驱动;否则更简单的投影就足够了。
- 读取模型是投影,不是事实来源。如果你发现自己先读取、再通过读取模型写回,就违背了 CQRS。
反模式
- 使用单一模型实现“CQRS”。 有两个名为
commands/和queries/的文件夹,但它们读写的是同一张表,没有任何分离。这只是做戏。 - 读模型变成直写模型。 代码绕过写模型,直接写入读取存储。两个模型会在无声无息中逐渐偏离。
- 向 UI 隐瞒最终一致性。 用户进行了更改,UI 却显示过时数据,用户会以为更改失败了。应告知用户延迟情况。
- 读模型迁移过重。 每次架构变更都需要完整重新投影,过程缓慢且风险较高。对读模型进行版本管理,并并行运行迁移。
- 因写读一致而采用(没有不对称性)。 只有纯粹的额外开销,没有任何收益。
另请参阅
arch-event-driven— 事件通常会将写入传播到读模型投影。arch-event-sourcing— 当写入端将事件作为事实来源存储时,CQRS 自然形成读取端。ddd-aggregate— CQRS 写模型的典型形态。ddd-repository— 读取端通常不会通过领域仓储读取;它们使用独立的查询对象。
参考资料
- Khononov,Learning DDD,第 8 章(CQRS)。
- Greg Young,“CQRS Documents by Greg Young”——对 CQRS 的最初阐述。
- Vaughn Vernon,Implementing Domain-Driven Design——第 4 章介绍了 CQRS。
评论
支持 Markdown