跳到正文

指南

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 内部做的事:

  1. 用 SyncInTxn 与 authorizer 调用 sequent.New。
  2. RegisterEvents(app.Events)。
  3. store 来自 StoreFactory,它会拿到应用的事件 registry。
  4. 设置 EnableTenancyMW 时先加入 tenancy 中间件,再加入 idempotency 中间件,保证此时 Meta.TenantID 已填充。
  5. 设置 Repo.Store、Repo.Publisher、Service.Tx、Service.Repo。
  6. 注册聚合工厂、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 不一致,以源码为准。