安全
事件演进
随时间修改 go-sequent 事件的兼容性策略:upcaster 规则、版本化策略、兼容性矩阵、禁止事项与测试要求。
事件演进
事件历史会存很多年。本页说明:在不改写历史的前提下,如何安全地改事件形状。
原则
- **事件是不可变的事实。**优先用新的事件类型或 upcaster,而不是改写历史。
- **JSON payload 是默认编码。**信封字段放在存储列里。可行时,事件体应省略冗余的信封键。
- **过去式、小而扁平的事件。**重命名类型需要 registry 条目加 upcaster,或者做数据迁移。
- **永不导入或迁移 Ruby Sequent 的数据库。**Sequent 只是概念上的灵感,没有 dump 或线协议兼容。
Upcaster 规则
Upcaster 位于 event/upcast,在加载事件时运行。
| 规则 | 说明 |
|---|---|
| 按已存储类型注册 | 每个已存储类型名一个 upcaster。Chain.Register 在 FromType 重复时 panic。 |
| 链式执行直到稳定 | 默认上限 8 跳,超出会返回错误,所以要避免环。 |
| 指向当前类型 | 最终类型必须已在 event.Registry 注册。 |
| 纯转换 | 不做 I/O,确定性的 JSON 进、JSON 出。 |
| 加载时接入 | 在 LoadStream 之前调用 store.SetUpcasters(chain)。 |
示例:把字段 amt 改名为 amount,把类型 bank.MoneyDeposited.v0 改名为 bank.MoneyDeposited。
app.RegisterUpcaster(upcast.FuncUpcaster{
From: "bank.MoneyDeposited.v0",
Fn: func(p json.RawMessage) (string, json.RawMessage, error) {
var m map[string]json.RawMessage
if err := json.Unmarshal(p, &m); err != nil {
return "", nil, err
}
if v, ok := m["amt"]; ok {
m["amount"] = v
delete(m, "amt")
}
out, err := json.Marshal(m)
return "bank.MoneyDeposited", out, err
},
})
store.SetUpcasters(app.Upcasters)
金额全程保持为 decimal 字符串。upcaster 只搬运这个值,不解析它。
版本化策略
- **新增字段。**使用可选的 JSON 键,并在 applier 里给默认值。不需要 upcaster。
- **重命名或重塑。**写一个从旧类型名出发的 upcaster。或者保留类型名,只转换 payload。
- **语义破坏。**以补偿的方式新增一个事件类型,例如
PaymentReversed,旧事件保持原样。 - **快照。**当聚合状态形状发生不兼容变化时,提升
SnapshotVersion,删除或忽略旧快照。见 快照。
兼容性矩阵
| 变更 | 事件 | 快照 | Projector |
|---|---|---|---|
| 新增可选字段 | 可以 | 通常可以 | 处理缺失值 |
| 重命名字段 | Upcaster | 提升版本 | Upcaster 或双读 |
| 重命名事件类型 | Upcaster 加 registry | 由事件重建 | 追上之前两种都处理 |
| 删除字段 | 旧事件中保留,applier 忽略 | 必要时提升版本 | 读模型里不再写入该字段 |
| 金额表示 | 必须保持 decimal 字符串,绝不用 float64 |
同左 | 同左 |
禁止事项
- 为了业务更正而静默地
UPDATE或DELETE事件行。请使用补偿事件。 - 把事件签名作为框架功能。框架实现的是 hash chain(按聚合串联事件哈希,用来发现篡改)。
- 把加密销毁作为 GDPR 路径。删除只通过 Tombstone 完成:不删历史事件,而是追加一条标记事件,让读模型不再暴露个人数据。
- 把 Ruby Sequent 的 SQL dump 加载到 go-sequent 的 schema 里。
修改已存储的事件行同样会破坏 hash chain,LoadStream 会返回 ErrHashChainTamper。见 EventStore。
测试要求
- 为每个生产环境的 upcaster 写单元测试。
- 注册 upcaster 后,用
LoadStream重放一个夹具事件流。 - 类型变更后重建 Projector(读模型),见 读模型重建。
相关
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。