Chapter 10. Event sourcing: a functional approach to persistence

覆盖functional storage view、event sourcing basics、write/projection/snapshot architecture与immutable storage tradeoffs,把state fold和append effect分开。

学习目标

  • 能比较overwrite、immutable snapshots、event log与hybrid storage保留的信息、query和evolution成本
  • 能设计fold events -> statestate + command -> eventspure core,复现replay/idempotency
  • 能分析expected-version append、projection、snapshot与event evolution architecture,设计fault/rebuild gates

机制总览

Chapter 10. Event sourcing: a functional approach to persistence:机制路径

  1. 1

    为什么Persistence也可以保存Change而不只保…

    传统CRUD覆盖latest row,过去发生什么需另建audit。Event sourcing把domain facts append为immutable log,current state由ordered events fold得到。它天然适合functional transition/repl…

  2. 2

    Thinking functionally about d…

    Functional view把storage reads当facts/snapshots,把writes当new facts/versions,而非远程mutable variables。Overwrite简单、query直接;immutable snapshots保留versions;event…

  3. 3

    Event sourcing basics

    核心分两functions: Fold(initial, events) - state 与 Decide(state, command) - events/error 。Apply event应deterministic且无I/O;Decide只产生facts,不append。Shell load…

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 10. Event sourcing: a functional approach to persistence:机制与证据

切换《Chapter 10. Event sourcing: a functional approach to persistence》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么Persistence也可以保存Change而不只保…

传统CRUD覆盖latest row,过去发生什么需另建audit。Event sourcing把domain facts append为immutable log,current state由ordered events fold得到。它天然适合functional transition/repl…

可核验证据

以确定输入重复运行「为什么Persistence也可以保存Change而不只保…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

学完《Chapter 10. Event sourcing: a functional approach to persistence》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 10. Event sourcing: a functional approach to persistence:失效与核验

为什么Persistence也可以保存Change而不只保…

典型失效

若把「为什么Persistence也可以保存Change而不只保…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「为什么Persistence也可以保存Change而不只保…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Thinking functionally about d…

典型失效

若把「Thinking functionally about d…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「Thinking functionally about d…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Event sourcing basics

典型失效

若把「Event sourcing basics」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「Event sourcing basics」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么Persistence也可以保存Change而不只保存Current State

传统CRUD覆盖latest row,过去发生什么需另建audit。Event sourcing把domain facts append为immutable log,current state由ordered events fold得到。它天然适合functional transition/replay,但引入event schema permanence、projection lag、concurrency和operational complexity,不是通用升级。

先预测:append-only就等于event sourcing吗;replay同events必然同state吗;snapshot可随意覆盖吗;projection exactly-once是否由framework保证;event可按class rename直接迁移。答案都是否。

Thinking functionally about data storage

Functional view把storage reads当facts/snapshots,把writes当new facts/versions,而非远程mutable variables。Overwrite简单、query直接;immutable snapshots保留versions;events保留intent/history;hybrid用events+snapshots/projections平衡load和query。

overwrite: id -> latest row
snapshot:  (id, version) -> state
events:    (streamId, sequence) -> immutable fact
分步1 / 3

切换overwrite、snapshot、events与hybrid

Event sourcing basics

核心分两functions:Fold(initial, events) -> stateDecide(state, command) -> events/error。Apply event应deterministic且无I/O;Decide只产生facts,不append。Shell load stream、fold、decide,再以expected version atomic append。

AccountState Fold(IEnumerable<AccountEvent> events) =>
    events.Aggregate(AccountState.Empty, Apply);
 
Either<Error, IEnumerable<AccountEvent>> Decide(
    AccountState state, Withdraw command) =>
    state.CanWithdraw(command.Amount)
        ? Right(new FundsWithdrawn(command.Id, command.Amount))
        : Left(Error.InsufficientFunds);
分步1 / 3

切换initial、apply、decide与replay

Architecture of an event-sourced system

Write path:load snapshot/tail、fold、decide、append(expectedVersion)。Conflict表示concurrent command基于stale state,需reload/redecide或返回conflict,不能盲重试旧events。Read path由projection消费events构建optimized views,通常eventually consistent。

Snapshot只是optimization,必须能验证stream/version和schema;删除events会改变source of truth。Projection consumer需checkpoint、idempotent update、duplicate/out-of-order policy与full rebuild。Outbox/subscription保证也要明确,不宣称神奇exactly-once。

var stream = await store.Load(streamId, token);
var state = Fold(stream.Events);
var pending = Decide(state, command);
await store.Append(streamId, stream.Version, pending.Right, token);
分步1 / 3

切换write、projection、snapshot与migration

Operational replay gate

上线前把rebuild写成可重复runbook:在隔离read model从零消费golden streams,记录events/sec、lag、poison event、retry与checkpoint advance,完成后以golden queries和aggregate totals对比live projection。遇到unknown event不能跳过后继续宣称成功;应隔离stream、保留offset和原因,修复reader/upcaster后从安全checkpoint恢复。容量评估同时覆盖全量replay时长、双写切换窗口、磁盘峰值与回滚路径,确保“能重放”不是只在unit test成立。

Comparing different approaches to immutable storage

Event sourcing适合history本身有业务价值、复杂state transition需audit/replay、多projections受益的domain。Versioned snapshots适合point-in-time但不需intent;append audit+CRUD适合记录变更者;temporal DB提供system-time query。按requirements、team ops和schema evolution选择。

Event schema一旦发布就是长期contract。Prefer stable semantic events;rename/internal refactor不改wire name。需要upcaster/new event version时保留old reader与golden streams。

本章回顾:Pure Fold不等于简单Operations

  1. Event sourcing保存domain facts,state由ordered deterministic fold重建。
  2. Decide产生events,shell以expected version append,I/O不进入Apply。
  3. Projection、snapshot是derived optimization,需要checkpoint、idempotency和rebuild。
  4. Event schema是长期contract,internal rename不能破坏history。
  5. CRUD/audit/snapshot/events各有tradeoff,按domain history价值选择。

练习

问题 1:并发两次withdraw怎样避免lost update?

问题 2:Projection rebuild需要哪些证据?

问题 3:何时不应选择Event Sourcing?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

event-sourced state
immutable storage model
deterministic event fold
expected-version append
event evolution contract

原版目录概念补充核对

以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。

Architecture of an event-sourced system:机制、边界与证据

Chapter 10. Event sourcing: a functional approach to persistence中的Architecture of an event-sourced system应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…