SuperAPI AI 工具集

arch-event-sourcing

编程与开发

查看项目

更新于 10-16 16:26

技能介绍

事件溯源是一种持久化架构模式:不保存当前状态,而是把改变状态的每个事件按顺序写入仅追加(append-only)、不可变、权威的事件日志作为事实来源;当前状态通过对事件做折叠(fold)重放推导得出。

事件存储 事件存储是仅追加的事件日志,每个事件带有聚合 ID、序列号和时间戳。写入通过 append(events) 完成,读取某个聚合的历史通过 eventsFor(aggregateId)。

重建状态 聚合的当前状态是其事件的折叠结果:state = events.fold(initial) { acc, event -> apply(acc, event) }。处理新命令时:加载该聚合的事件、重放得到当前状态、执行命令、追加新事件。内存中的状态是临时的,磁盘上的事件才是真相。

快照 对于生命周期很长(多年事件)的聚合,从起点重放会变慢。快照是按事件编号 N 周期性保存的状态;加载时使用快照加上 N 之后的事件。快照只是优化手段,不是事实来源——事件仍是。

事件版本化 事件会永久存在,其 schema 必须向前兼容地演进。策略包括:仅做增量变更(新字段可选、绝不设为必填);upcaster(读取时把旧版本事件转换为当前形态的代码);弱 schema(JSON 加宽容解析,以编译期安全换取灵活性)。

与 CQRS 搭配 事件溯源的写入天然产生可投影到读模型的事件。多数生产级事件溯源系统用 CQRS 处理查询,因为直接查询事件流并不方便。

运维考量 事件存储的可用性至关重要,它是事实来源;重放、投影重建、schema 迁移等工具必须在依赖之前就存在,项目中途再建会很痛苦;存储单调增长,需要提前规划;没有快照、事件数以百万计的聚合会难以支撑。

何时使用 适合:审计/监管要求完整变更历史;业务本身就以"发生了什么"思考(银行交易、理赔调整、供应链事件);需要回答时间点查询("某日期时状态如何");需要同一数据的多个投影,且从事件物化比手工维护更容易;与 CQRS 天然搭配(当读取需要与规范事件流不同的形态时)。

何时不使用 不适合:领域不以事件思考,强行套用事件形态成本高于收益;当前状态已足够且很少查询历史,CRUD 更简单;团队不熟悉最终一致性、重放语义或事件版本化,运维痛苦会很严重;仅仅因为微服务流行、读到过相关文章就做事件溯源——这不是理由。

决定启发式 只在确实需要审计、时间查询或多重投影的聚合上做事件溯源,不要一律套用;快照间隔是调优旋钮,可从 100-500 个事件起步,再按重放性能调整;把事件 schema 当作公共 API,为其做版本管理,绝不破坏;在生产依赖事件溯源之前,先构建好重放、重建、迁移等运维工具集。

反模式 反射式地对整个系统做事件溯源(多数子域并不需要,通常只应应用于有强审计需求的核心域);可变事件(编辑过去事件来"修复"历史,会破坏整个模式,正确做法是追加补偿事件);把事件当 DTO(只有"字段变了"而无语义,应是过去时、有业务含义的领域事件);热聚合无快照地重放(每个命令都要数秒到更久);没有 upcaster 的 schema 破坏(旧事件变得不可读,历史实际丢失)。

相关主题 与 CQRS(从事件投影出读模型)、事件驱动(事件作为跨上下文通信,常为同一批事件)、DDD 领域事件(战术级事件概念)、DDD 聚合(事件溯源聚合在命令处理中产生事件)相关。参考资料包括 Khononov《Learning DDD》第 7 章、Vaughn Vernon《Implementing Domain-Driven Design》第 8 章,以及 Greg Young 关于事件溯源的演讲与文章。