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
- Command service for
Executeand its options. - Command handler and Repository for the middle of the loop.
- Event store for OCC, unique keys and the hash chain.
- Publisher, Projector and Workflow for the end of the loop.
- Write-path consistency for the guarantees together.
Docs track the go-sequent library. When APIs drift, the source wins.