参考
锁定决策
决定 go-sequent 行为的设计决策:SyncInTxn 账本读模型、decimal Money、hash chain、共享 schema Tenancy、Tombstone GDPR、Postgres Outbox、纯库形态与 MIT 许可。
锁定决策
这些选择在实现之前就已确定,代码严格遵循它们,其余文档也以它们为前提。如果你的需求与其中某一条冲突,go-sequent 多半不是合适的工具,否则你得自己维护一个分支。
十项决策
| # | 决策 | 含义 |
|---|---|---|
| Q1 | 账本读模型使用 SyncInTxn |
在写事务里同步更新余额等读模型 |
| Q2 | 金额使用 decimal | types.Money,禁用 float64 |
| Q3 | 用 hash chain 发现篡改(按聚合串联事件哈希) | 按聚合;没有事件签名 |
| Q4 | Tenancy(多租户)为共享 schema + tenant_id |
没有 schema-per-tenant 或 database-per-tenant |
| Q5 | GDPR 只做 Tombstone | 不做 crypto-shredding |
| Q6 | Outbox 为 Postgres 表轮询 | 框架里没有 LISTEN/NOTIFY 或消息中间件 |
| Q7 | 纯库 | 仓库里没有 HTTP 或 gRPC |
| Q8 | 快照使用 JSON | 独立版本管理 |
| Q9 | 永不导入 Ruby Sequent 数据库 | 仅在概念上受其启发 |
| Q10 | 以 MIT 许可证开源 | 仓库与文档公开 |
Q1:账本读模型用 SyncInTxn
Projector(把事件写成查询用读模型,例如余额表)与 Workflow 在写事务内运行,任何失败都会整体回滚。结果是余额永远不会落后于引起它的事件——提交返回后立刻能读到自己刚写的数据。
代价是 Projector 的工作计入写延迟,缓慢或出错的 Projector 会阻塞写入。账本 Projector 要保持小而确定。非账本路径可以通过 Outbox 做异步投递。见 Publisher。
Q2:decimal 金额
金额是带币种的 decimal 数额,以字符串序列化。框架不使用整数最小单位。金额字段禁用 float64,可用 testkit.BanFloat64Money 与 types.AssertNoFloatMoney 检查。舍入策略属于你的领域,历史金额绝不用浮点数重算。见 Money。
Q3:hash chain,没有签名
每个聚合事件流有一条基于规范事件字段的 SHA-256 链。篡改可在加载时或通过 VerifyChain 发现。事件签名不在范围内。数据库访问控制、加密备份与可选的 WORM 存储,是运维层面的补充手段。见 EventStore 与威胁模型。
Q4:共享 schema + tenant_id
只有一套表。每条命令、聚合、Idempotency 记录和读模型都带租户,唯一键 scope 包含租户。partition_key 可以映射到租户,用于同一 schema 内的物理分区。行级安全是可选的,由你自己决定。见 Tenancy(多租户) 与 分区。
Q5:仅 Tombstone 的 GDPR
响应删除请求时,追加一条标记事件(如 gdpr.SubjectTombstoned),由 Projector 清除或不再提供个人数据——之后读模型不再暴露个人数据。事件保持不可变。不提供 crypto-shredding。物理删除事件行属于特殊运维操作,需要事件流修复工具,且除非处理得当否则会破坏 hash chain。可选的 PayloadProtector 支持对 PII 做字段加密,但不涉及删除或真实性。
Q6:Postgres Outbox 轮询
使用 Outbox 时,消息是写事务里写入 Postgres 表的行,通过 Claim、Ack、Nack 轮询消费。投递语义是至少一次,消费者必须保证同一业务键重复提交只生效一次。见 Publisher。
Q7:纯库
go-sequent 不带 HTTP 服务、gRPC stub、UI 或 ORM。CLI 是运维工具,用于迁移、replay 的 schema 阶段与 Outbox 轮询。你把 command.Service 嵌入自己的进程。
Q8:JSON 快照
快照载荷是 JSON,带 SnapshotVersion,不兼容变更时提升版本号。见 Snapshotting。
Q9:永不导入 Ruby Sequent 数据库
概念模型来自 Sequent,schema、序列化与 API 不是。不提供 dump 或线协议兼容,也没有此类计划。Upcaster 只用于你自己的事件历史。见 相对 Sequent Ruby。
Q10:开源,MIT 许可证
项目以 MIT 许可证开源,文档公开。欢迎在 github.com/go-sequent/go-sequent 提交 issue 与贡献。
基线决策(D1 至 D10)
| ID | 决策 |
|---|---|
| D1 | 模块路径为 github.com/go-sequent/go-sequent |
| D2 | PostgreSQL 是第一个也是唯一的生产事件库后端 |
| D3 | 默认发布模式是写事务内的同步 Projector |
| D4 | UnitOfWork 是 context 级,而不是进程全局 |
| D5 | 命令与事件默认使用 JSON 编码 |
| D6 | 业务更正用补偿事件,而不是可变历史 |
| D7 | 金额字段禁用 float64,金额类型为 decimal |
| D8 | 显式注册 Handler,不强制使用反射自动注册 |
| D9 | 先设计,后实现 |
| D10 | 仅在概念上受 Sequent 启发,永不导入 Ruby Sequent 数据库 |
背后的原则
- 事件是事实来源,读模型是可丢弃的。
- 聚合在处理命令时不读取其他聚合的状态。
- 命令、事件与同步更新的读模型共享一个数据库事务,异步模式需要显式启用。
- 默认开启金融相关特性:decimal 金额、Idempotency 键、每条命令的审计元数据。
- 用朴素的 Go:接口、构造函数、
context.Context,没有全局可变单例。
相关
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。