Skip to content

Reference

Wiring the façade

How to assemble a go-sequent application with the sequent package: sequent.New, options such as WithStore, WithIdempotency, WithAuthorizer and WithObs, Register methods, and middleware order.

Wiring the façade

Package: sequent

The sequent package builds a ready-to-use App: event registry, upcaster chain, command router, repository, publisher and command.Service. Your main configures it and registers your domain.

What New builds

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

App fields:

Field Type Role
Config sequent.Config Options. PublisherMode is SyncInTxn, the only mode and the default.
Events *event.Registry Event type registry
Upcasters *upcast.Chain Payload upcasters
Commands *command.Router Command to handler routing
Repo *aggregate.Repository Loads and commits aggregates
Publisher *publish.SyncInTxn Projectors and workflows
Service *command.Service The entry point: Service.Execute
Obs *obs.Instruments Optional telemetry

Options

Option Effect
WithStore(store, tx) Sets Repo.Store, Service.Tx, and Repo.Publisher. If the store also implements eventstore.SnapshotStore, sets Repo.Snapshots.
WithAuthorizer(auth) Appends security.Filter{Auth: auth} to Service.Filters. See AuthZ.
WithIdempotency(store, strict) Appends idempotency middleware to Service.Middleware. See Idempotency.
WithObs(inst) Sets App.Obs and Service.Obs. See OpenTelemetry.

Options apply in the order you pass them. Middleware and filters append, so order matters.

Registering your domain

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)
Method Purpose
RegisterAggregate(type, factory) Lets the repository rebuild aggregates by type name
RegisterHandler(h) Maps each command type in h.Handles() to h. Panics on a duplicate type.
RegisterProjector(h) Registers on SyncInTxn. Included in projection rebuild.
RegisterWorkflow(w) Registers on SyncInTxn. Excluded from rebuild.
RegisterUpcaster(u) Adds to app.Upcasters

All registration is explicit. There is no reflection-based auto-registration.

The store and the registry

The store needs the app’s event registry to decode events, and New creates that registry. So a common sequence is:

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

WithStore does the first four assignments for you when you already hold a store. The bank demo takes a StoreFactory that receives app.Events, so the store always shares the registry:

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

Middleware order

Service.Middleware runs outer-first, and Service.Filters run after the whole chain. A typical order:

  1. tenancy.Middleware: sets Meta.TenantID from the context.
  2. Optionally security.Middleware(auth), if you need AuthZ on replays too.
  3. Idempotency middleware: needs the tenant.
app.Service.Middleware = append(
    []command.Middleware{tenancy.Middleware},
    app.Service.Middleware...,
)

Filters (from WithAuthorizer) then run before Validate.

Running a command

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

Bank helper

bank.NewDemoApp(bank.DemoOptions{...}) does all of the above for the bank sample, and returns a DemoApp with App, Projector and Workflow. Read examples/bank/app.go as a worked example of the whole wiring. See Bank example.

No transport

The façade does not start servers. Call Service.Execute from your own HTTP, gRPC or queue handlers.

Docs track the go-sequent library. When APIs drift, the source wins.