指南
Bank 示例
go-sequent 两个 bank 示例导览:examples/bank 内存演示,以及 examples/bankpg 的 Postgres 版本(SQL 余额、Tenancy、Idempotency 与 AuthZ)。
Bank 示例
仓库里有两个领域相同的 bank 示例。先读第一个了解各部分的形状,再读第二个看基于 Postgres 的服务长什么样。
| 路径 | 角色 |
|---|---|
examples/bank/ |
演示。内存 SyncInTxn 余额,集成测试始终可跑。 |
examples/bankpg/ |
生产形态。Postgres 事件库,余额经 projector.Persistor 写入 SQL。 |
演示版覆盖什么
examples/bank 实现四个命令:开户、存款、取款、转账。涉及:
- 通过
types.Money使用 decimal 金额。 - 租户级唯一键:同一租户下一个 owner 只能有一个账户。
- AuthZ:默认拒绝的白名单。
- 租户级 Idempotency(同一业务键重复提交只生效一次)。
SyncInTxn余额 Projector(在写事务里同步更新读模型)。- 一个注册 AfterCommit 钩子的 Workflow。
Account聚合的 JSON 快照。
文件
| 文件 | 内容 |
|---|---|
events.go |
五个事件类型与 RegisterEvents |
account.go |
Account 聚合、applier、命令方法、UniqueKeys、快照方法 |
commands.go |
OpenAccount、Deposit、Withdraw、Transfer(含 Validate) |
handler.go |
把命令路由到聚合方法的 Handler |
projector.go |
BalanceProjector(内存) |
workflow.go |
使用 RegisterAfterCommit 的 NotifyWorkflow |
app.go |
NewDemoApp、DemoOptions、AllowBankCommands |
领域建模会逐步搭出这些文件。
用 NewDemoApp 接线
demo, err := bank.NewDemoApp(bank.DemoOptions{
StoreFactory: func(reg *event.Registry) (eventstore.Store, eventstore.TxManager, eventstore.SnapshotStore, error) {
s := memory.New(reg)
return s, s, s, nil
},
Idempotency: idempotency.NewMemory(),
Authorizer: bank.AllowBankCommands(),
EnableTenancyMW: true,
})
ctx := tenancy.WithTenant(ctx, tenantID)
_ = demo.App.Service.Execute(ctx, &bank.OpenAccount{ /* ... */ })
bal, _ := demo.Projector.Balance(accountID)
NewDemoApp 内部做的事:
- 用
SyncInTxn与 authorizer 调用sequent.New。 RegisterEvents(app.Events)。- store 来自
StoreFactory,它会拿到应用的事件 registry。 - 设置
EnableTenancyMW时先加入 tenancy 中间件,再加入 idempotency 中间件,保证此时Meta.TenantID已填充。 - 设置
Repo.Store、Repo.Publisher、Service.Tx、Service.Repo。 - 注册聚合工厂、Projector、Workflow 与 Handler。
AllowBankCommands() 返回 security.DenyByDefault,只放行四个 bank 命令类型,其余一律拒绝。
生产形态示例
examples/bankpg 领域不变,换掉基础设施:
- 用
Open打开 Postgres 事件库,用store.Migrate建表。 - 表
bank_account_balances归示例所有,不属于核心 schema。 SQLBalanceProjector经projector.Persistor写入。Persistor 加入写事务,所以余额仍在事务内更新。- tenancy 中间件、Postgres Idempotency、AuthZ 白名单。
- 可通过
Options.Instruments传入obs.Instruments。
先应用核心 schema,再应用示例自己的读模型 DDL:
store.Migrate(ctx)
bankpg.MigrateProjection(ctx, store.Pool)
已知缺口
示例对没做的部分是坦率的:
- 没有行级安全(RLS)策略。
- 没有 HTTP / gRPC,go-sequent 是纯库。
- 示例里的读模型重建是测试用的「手动 truncate + 事件流回放」辅助函数,不是带 golive 的完整
projector/replay流水线。见 读模型重建。 - 默认没有接 Outbox。账本保持
SyncInTxn;需要异步扇出时自行包装publish.WithOutbox。 - OpenTelemetry 的
RecordEvents计数器不会在追加时自动调用。
运行测试
# 始终可跑。没有 DSN 时 Postgres 测试跳过。
go test ./examples/bank/ ./examples/bankpg/ -count=1
# 带 Postgres
GO_SEQUENT_PG_DSN='postgres://user:pass@localhost:5432/go_sequent?sslmode=disable' \
go test ./examples/bankpg/ -count=1 -run TestIT_BankPG
覆盖场景:命令 → 事件 → SyncInTxn 读模型、Idempotency 重试、AuthZ 拒绝、租户级唯一 owner、OCC。内存版始终运行;SQL 余额版在设置 DSN 时运行。
当作模板
新服务可以复制 examples/bankpg 再替换领域。接线顺序保持不变:tenancy 中间件在前,然后是 idempotency,AuthZ 作为 filter,任何会影响余额的读模型都用 SyncInTxn。非账本的副作用放到 Workflow 的 AfterCommit 钩子或 Outbox。
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。