跳到正文

参考

sequent 门面接线

用 sequent 包装配 go-sequent 应用:sequent.New、WithStore / WithIdempotency / WithAuthorizer / WithObs、Register 方法与中间件顺序。

sequent 门面接线

包:sequent

sequent 包构造一个开箱即用的 App:事件 registry、upcaster 链、命令路由、Repository、Publisher 与 command.Service。你的 main 负责配置并注册领域对象。

New 构造了什么

app := sequent.New(sequent.Config{PublisherMode: sequent.SyncInTxn},
    sequent.WithStore(store, tx),
    sequent.WithAuthorizer(auth),
    sequent.WithIdempotency(idemp, false),
    sequent.WithObs(inst),
)

App 的字段:

字段 类型 作用
Config sequent.Config 配置。PublisherMode 为 SyncInTxn(写事务内同步更新读模型),这是唯一模式,也是默认值。
Events *event.Registry 事件类型 registry
Upcasters *upcast.Chain 载荷 upcaster
Commands *command.Router 命令到 Handler 的路由
Repo *aggregate.Repository 加载并提交聚合
Publisher *publish.SyncInTxn Projector 与 Workflow
Service *command.Service 入口:Service.Execute
Obs *obs.Instruments 可选遥测

选项

选项 作用
WithStore(store, tx) 设置 Repo.Store、Service.Tx 与 Repo.Publisher;若 store 同时实现 eventstore.SnapshotStore,还会设置 Repo.Snapshots。
WithAuthorizer(auth) 把 security.Filter{Auth: auth} 追加到 Service.Filters。见 AuthZ。
WithIdempotency(store, strict) 把 Idempotency(幂等)中间件追加到 Service.Middleware。见 Idempotency。
WithObs(inst) 设置 App.Obs 与 Service.Obs。见 OpenTelemetry。

选项按传入顺序应用。中间件和过滤器是追加的,所以顺序很重要。

注册领域对象

bank.RegisterEvents(app.Events)
app.RegisterAggregate("bank.Account", func(id uuid.UUID) aggregate.Root { return bank.NewAccount(id) })
app.RegisterHandler(&bank.Handler{Repo: app.Repo})
app.RegisterProjector(balanceProjector)
app.RegisterWorkflow(notifyWorkflow)
app.RegisterUpcaster(myUpcaster)
方法 作用
RegisterAggregate(type, factory) 让 Repository 能按类型名重建聚合
RegisterHandler(h) 把 h.Handles() 中的每个命令类型映射到 h;类型重复会 panic
RegisterProjector(h) 注册到 SyncInTxn;Projector 把事件写成查询用读模型,并参与读模型重建
RegisterWorkflow(w) 注册到 SyncInTxn,不参与重建
RegisterUpcaster(u) 加入 app.Upcasters

所有注册都是显式的,没有基于反射的自动注册。

store 与 registry

store 需要应用的事件 registry 才能解码事件,而 registry 由 New 创建。常见顺序是:

app := sequent.New(sequent.Config{PublisherMode: sequent.SyncInTxn})
bank.RegisterEvents(app.Events)

store, err := postgres.Open(ctx, dsn, app.Events)
if err != nil { /* ... */ }
if err := store.Migrate(ctx); err != nil { /* ... */ }
store.SetUpcasters(app.Upcasters)

app.Repo.Store = store
app.Repo.Snapshots = store
app.Repo.Publisher = app.Publisher
app.Service.Tx = store
app.Service.Repo = app.Repo

当你已经持有 store 时,WithStore 会替你完成前四个赋值。bank 演示接收一个会拿到 app.Events 的 StoreFactory,所以 store 始终与应用共用 registry:

type StoreFactory func(reg *event.Registry) (eventstore.Store, eventstore.TxManager, eventstore.SnapshotStore, error)

中间件顺序

Service.Middleware 外层先行,Service.Filters 在整条链之后运行。典型顺序:

  1. tenancy.Middleware:从 context 设置 Meta.TenantID。
  2. 可选 security.Middleware(auth)——若重放时也需要 AuthZ。
  3. 幂等中间件:需要租户信息。
app.Service.Middleware = append(
    []command.Middleware{tenancy.Middleware},
    app.Service.Middleware...,
)

过滤器(来自 WithAuthorizer)随后在 Validate 之前运行。

执行命令

ctx = tenancy.WithTenant(ctx, tenantID)
err := app.Service.Execute(ctx, &bank.Deposit{AccountID: id, Amount: types.MustMoney("10.00", "CNY")})

Bank 辅助函数

bank.NewDemoApp(bank.DemoOptions{...}) 为 bank 示例完成上述全部接线,并返回带有 App、Projector 与 Workflow 的 DemoApp。可以把 examples/bank/app.go 当作完整接线的参考。见 Bank 示例。

不含传输层

门面不会启动任何服务。请在自己的 HTTP、gRPC 或消息队列处理器里调用 Service.Execute。

相关

文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。