CQRS 与事件溯源
CQRS 与事件溯源围绕 7 个章专属概念,以边界模型、决策轨迹、违规恢复和来源分层完成验收。
学习目标
- 能解释“CQRS 与事件溯源”如何分别判断读写模型分离与事件日志作为事实源的必要性,并设计同步、重放和一致性边界
- 能逐项定位 命令模型、查询模型、读模型同步、事件日志作为事实源、重放与快照、最终一致性、CQRS 与事件溯源可独立采用,并说明每个概念在当前来源边界内承担什么责任
- 能按 接收命令 → 验证不变量 → 追加领域事件 → 更新查询投影 → 重放核对状态 推演“订单状态历史”,检查“命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写”
- 能注入“把 CQRS 等同于双数据库,把事件溯源等同于普通审计日志,并默认两者必须同时采用”,依据命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口定位越界、撤销并用同一情境重放
为什么从“先证明单模型确有读写张力或历史重建需求,再分别引入 CQRS、事件溯源及其必要基础设施”开始
分别判断读写模型分离与事件日志作为事实源的必要性,并设计同步、重放和一致性边界。本页先画责任和依赖,再沿具体情境执行决策,最后主动制造一个边界违规;这种顺序把术语变成可以验证、推翻和恢复的设计合同。
“CQRS 与事件溯源”不是技术采购建议。它处理的是规则放在哪里、模型在哪个语境内成立、哪些变化可以被隔离,以及团队如何判断一条边界已经被穿透。最终功能可运行,只能证明快乐路径存在,不能证明结构允许安全演进。
来源、版次与课程边界
本单元是明确标注的专题扩展,不属于两本主干书的共同原始目录。CQRS 与 Event Sourcing 分别依据 Fowler 的两篇原始文章独立核对。
本专题不是某一本既有书的中文版,也不是把多位作者的文章拼成“原著章节”。课程主干分别取自 Eric Evans 的领域驱动设计体系与 Robert C. Martin 的整洁架构体系;CQRS、事件溯源和六边形架构始终标为扩展。以下链接承担不同事实责任:
- Martin Fowler: CQRS:核对命令与查询模型分离、适用压力、复杂度和同步代价
- Martin Fowler: Event Sourcing:核对事件序列作为事实源、状态重建、重放和外部更新问题
本页采用独立中文重写。能从公开全文或授权摘要核对的定义才作为来源事实;项目情境、交互实验、取舍表和练习答案均为本站教学设计。
核心概念与可观察责任
命令模型
命令模型验证改变状态的业务意图并保护写侧不变量,不为查询方便而暴露可随意修改的数据结构。 在“订单状态历史”中,观察重点是命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写。
查询模型
查询模型针对读取场景塑形,可以预计算和反规范化;它不承担改变领域状态的职责。 在“高频报表读取”中,观察重点是命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口。
读模型同步
写侧结果通过事件或消息更新读投影,必须定义延迟、失败重试、幂等和重建方式,不能把同步过程当成自动可靠。 在“订单状态历史”中,观察重点是命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写。
事件日志作为事实源
事件溯源把对象发生的全部领域变化保存为事实序列,当前状态由事件重建;普通审计副本不具备这一权威地位。 在“高频报表读取”中,观察重点是命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口。
重放与快照
重放验证事件序列能恢复状态,快照只缩短恢复时间且必须能被丢弃重建;快照不能取代事件事实。 在“订单状态历史”中,观察重点是命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写。
最终一致性
读投影可能暂时落后于写侧,产品和接口必须声明可接受窗口、用户反馈和读己之写策略。 在“高频报表读取”中,观察重点是命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口。
CQRS 与事件溯源可独立采用
CQRS 关注命令和查询模型分离,事件溯源关注状态事实的存储方式;任一问题都不自动证明另一模式必要。 在“订单状态历史”中,观察重点是命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写。
一条可执行的设计判断
本页采用以下判断链:先证明单模型确有读写张力或历史重建需求,再分别引入 CQRS、事件溯源及其必要基础设施。正常情况下必须持续保持“命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写”;反例“把 CQRS 等同于双数据库,把事件溯源等同于普通审计日志,并默认两者必须同时采用”只改变一个关键条件,便于定位因果。
| 观察项 | 本页合同 |
|---|---|
| 最小正常情境 | 需要解释订单为什么从已支付转为退款完成 |
| 边界或迁移情境 | 复杂报表读取远多于订单写入且形状完全不同 |
| 必须保持 | 命令只表达改变意图,查询不产生领域副作用;采用事件溯源时事件日志不可被事后改写 |
| 首要违规 | 把 CQRS 等同于双数据库,把事件溯源等同于普通审计日志,并默认两者必须同时采用 |
| 验收证据 | 命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口 |
先预测,再操作三个本页实验
实验一:边界与模型地图
操作前先预测“订单状态历史”里哪一侧拥有规则、哪一侧只是技术或协作细节。切换概念和情境后,检查“先证明单模型确有读写张力或历史重建需求,再分别引入 CQRS、事件溯源及其必要基础设施”是否仍能解释三块区域的责任。
Boundary model
CQRS 与事件溯源:边界与责任地图
分别判断读写模型分离与事件日志作为事实源的必要性,并设计同步、重放和一致性边界
切换最小情境
定位正式概念
architecturedomaindesign-10 · 当前观察
命令模型:需要解释订单为什么从已支付转为退款完成
验证意图并形成不可改写的领域事实
↓
按顺序、幂等地构建可恢复状态
↓
面向读取场景并声明一致性窗口
情境验收
事件序列保留每次领域变化,重放得到同一当前状态和解释路径
易错边界与取舍
练习与答案
练习
问题 1:概念—边界—证据对照。 完成以下逐项核对:
- 命令模型:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- 查询模型:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- 读模型同步:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- 事件日志作为事实源:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- 重放与快照:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- 最终一致性:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
- CQRS 与事件溯源可独立采用:写出它所在的边界、允许的依赖方向、一个最小反例和一份可观察证据。
问题 2:最小情境推演。 怎样验证“订单状态历史”没有靠最终功能碰巧通过?
问题 3:替代方案与恢复。 注入“把 CQRS 等同于双数据库,把事件溯源等同于普通审计日志,并默认两者必须同时采用”后,怎样比较修复与更简单方案?
本页小结
- CQRS 与事件溯源的核心判断是:先证明单模型确有读写张力或历史重建需求,再分别引入 CQRS、事件溯源及其必要基础设施。
- 正常路径以“事件序列保留每次领域变化,重放得到同一当前状态和解释路径”验收,不能只检查接口返回成功。
- 边界路径以“先评估独立查询投影;无需因此自动采用事件溯源”验收,并保存命令合同、查询投影、事件顺序、处理幂等键、重放结果、快照版本与一致性窗口。
- 如果“把 CQRS 等同于双数据库,把事件溯源等同于普通审计日志,并默认两者必须同时采用”仍可静默通过,说明边界只是图示,没有成为可执行约束。