Skip to content

Introduction

Core loop

The go-sequent write path step by step: CommandService.Execute, middleware, AuthZ filters, handlers, Repository.Commit, SyncInTxn publish, transaction commit and AfterCommit hooks.

Core loop

Every state change in go-sequent goes through one call: CommandService.Execute(ctx, cmds...). Understanding what happens inside it explains most of the library.

Client / service entry
 │
 ▼
 CommandService.Execute(ctx, cmds...)
 │ Tx via WithinTx + UnitOfWork on ctx
 │
 │ for each command:
 ├─ EnsureMeta
 ├─ Middleware chain (tenancy, idempotency, ...)  outer first
 ├─ Filters (AuthZ)
 ├─ Command.Validate
 ├─ Router.Dispatch → CommandHandler
 │     Repo.Add / Repo.Load → aggregate method → Apply(event)
 ├─ Repository.Commit
 │     EventStore.Append  (OCC, unique keys, hash chain)
 │     Publisher.Publish  → Projectors → Workflows
 ▼
 Transaction commit → AfterCommit hooks

What you write, and what the library does

You write the commands, the handler, the aggregate, the events and the projectors. The library supplies the rest: command.Service, aggregate.Repository, the event store and publish.SyncInTxn.

Step Owner What happens
Transaction and UnitOfWork Library Execute opens a transaction through the store’s TxManager and puts a UnitOfWork on the context.
Middleware You choose Tenancy and idempotency are typical. They run outer-first.
Filters You choose AuthZ runs here. A denial stops the command.
Validate You Checks the command’s own shape.
Handler You Loads or creates aggregates and calls their methods.
Aggregate You Applies rules, then Applys events.
Commit Library Appends the changed aggregates’ events, enforcing optimistic concurrency, unique keys and the hash chain.
Publish Library Projectors run first, then workflows, inside the same transaction.
AfterCommit You Hooks registered through workflow.RegisterAfterCommit run once the transaction has committed.

Why one transaction

The events, the unique keys, the idempotency record and the ledger projections all commit together or not at all. If a projector returns an error, the events are not stored. This is the SyncInTxn guarantee, and it is why a balance read right after Execute is never stale.

Side effects that must not run on a rolled-back transaction, such as sending an email, belong in an AfterCommit hook, not in a projector.

Multiple commands in one call

Execute takes a list. All commands share the transaction and the UnitOfWork, so they either all commit or none do. Use that when two commands must be atomic. For one business operation touching two aggregates, one command that loads both is usually clearer. See the transfer in Modelling the domain.

Context-scoped UnitOfWork

The UnitOfWork lives in context.Context, one per Execute. Do not share it across goroutines. Run independent commands in independent Execute calls.

Where each step is documented

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