参考
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 在整条链之后运行。典型顺序:
tenancy.Middleware:从 context 设置Meta.TenantID。- 可选
security.Middleware(auth)——若重放时也需要 AuthZ。 - 幂等中间件:需要租户信息。
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 不一致,以源码为准。