入门
快速开始
安装 go-sequent,用内存 EventStore 跑通 bank 示例,执行第一批命令,体验 SyncInTxn 余额读模型、Idempotency 与 Tenancy(多租户)。
快速开始
本页安装模块、运行 bank 示例,并列出你在自己服务里要接的几块。下面全部用内存 EventStore,暂时不需要数据库。
安装
go get github.com/go-sequent/go-sequent@latest
需要 Go 1.24 及以上,MIT 许可证。
运行 bank 示例
仓库里有两个 bank 示例:
| 路径 | 角色 |
|---|---|
examples/bank/ |
演示。内存 SyncInTxn 余额,集成测试始终可跑。 |
examples/bankpg/ |
生产形态。Postgres 事件库 + SQL 表 bank_account_balances。没有 GO_SEQUENT_PG_DSN 时跳过。 |
跑测试:
go test ./...
go test ./examples/bank/ ./examples/bankpg/ -count=1
go test ./... 不需要 DSN,Postgres 相关测试会自动 skip。要把它们也跑上:
GO_SEQUENT_PG_DSN='postgres://user:pass@localhost:5432/go_sequent?sslmode=disable' \
go test ./examples/bankpg/ -count=1 -run TestIT_BankPG
第一批命令
bank.NewDemoApp 会组装好完整应用:CommandService、Handler、余额 Projector 和一个通知 Workflow。
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,
})
if err != nil {
log.Fatal(err)
}
ctx := tenancy.WithTenant(context.Background(), tenantID)
err = demo.App.Service.Execute(ctx, &bank.OpenAccount{
AccountID: accountID,
Owner: "alice",
Currency: "CNY",
Opening: types.MustMoney("100.00", "CNY"),
})
bal, _ := demo.Projector.Balance(accountID)
Execute 返回之后,demo.Projector.Balance 已经是新余额:Projector 与事件追加在同一事务里跑。也就是提交返回后立刻能读到自己刚写的数据。账本读模型因此走 SyncInTxn(在写事务里同步更新读模型)。见 名词释义。
StoreFactory 把应用的事件 registry 交给 store,store 才能在加载时解码事件。务必使用与 RegisterEvents 相同的 registry。
核心闭环五步
- 定义 Event,并在
event.Registry注册工厂。 - 聚合嵌入
aggregate.Base,状态只在 applier 里改。 - 定义 Command 与
command.Handler。 - 把 Projector 注册到
SyncInTxn,账本余额随写事务更新。 - 调用
CommandService.Execute(ctx, cmds...)。
核心闭环解释第 5 步内部发生了什么;领域建模为 Account 聚合实现第 1 到 4 步。
Money
使用 types.Money。金额以 decimal 字符串序列化,也不能由浮点数构造。
opening := types.MustMoney("100.00", "CNY")
rounded := opening.Round(2)
_ = types.AssertNoFloatMoney(myCommand) // 启动或测试时做检查
详见 Money。
租户级 Idempotency
同一业务键重复提交只生效一次:
idemp := idempotency.NewMemory() // 或 idempotency.NewPostgres(pool)
app := sequent.New(cfg, sequent.WithIdempotency(idemp, false))
cmd.Meta().TenantID = tenant
cmd.Meta().IdempotencyKey = "client-key-1"
成功之后,相同 (tenant_id, key) 的重试是空操作。详见 Idempotency。
AuthZ 与审计元数据
app := sequent.New(cfg, sequent.WithAuthorizer(security.DenyByDefault{
Allow: map[string]bool{"bank.Deposit": true},
}))
audit := command.AuditOf(cmd) // CommandID、TenantID、CorrelationID、Source 等
详见 AuthZ。
Outbox 与 Postgres 轮询
账本 Projector 仍然是 SyncInTxn。需要异步扇出时,用 outbox 包装 publisher:
ob := outbox.NewMemory() // 或 outbox.NewPostgres(pool)
pub := &publish.WithOutbox{Inner: app.Publisher, Writer: ob, Topic: "events"}
app.Repo.Publisher = pub
用 CLI 消费:
go run ./cmd/go-sequent outbox -dsn "$GO_SEQUENT_PG_DSN" -once
去掉 -once 即为常驻轮询。详见 Publisher。
运维 CLI
go run ./cmd/go-sequent migrate -dsn "$GO_SEQUENT_PG_DSN"
go run ./cmd/go-sequent replay -dsn "$DSN" -projector balances -stage prepare -tables t1
go run ./cmd/go-sequent outbox -dsn "$DSN" [-once]
详见 Migrations 与 CLI。
下一步
- go-sequent 是什么
- 领域建模
- Bank 示例
- 上生产前先读运维概览
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。