跳到正文

入门

快速开始

安装 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。

核心闭环五步

  1. 定义 Event,并在 event.Registry 注册工厂。
  2. 聚合嵌入 aggregate.Base,状态只在 applier 里改。
  3. 定义 Command 与 command.Handler。
  4. 把 Projector 注册到 SyncInTxn,账本余额随写事务更新。
  5. 调用 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 源码更新;若与 API 不一致,以源码为准。