Skip to content

Open source · Go 1.24+ · MIT

Event-sourced ledgers,
built for Go finance stacks

go-sequent is a CQRS and event sourcing library for payments, wallets, and ledger workloads. It keeps the write path boring and correct: one transaction, context-scoped UnitOfWork, SyncInTxn projections.

go get github.com/go-sequent/go-sequent@latest

Why teams reach for it

Inspired by Sequent’s domain loop, implemented as idiomatic Go. No Ruby database import, no web framework lock-in.

Ledger-first write path

Command, aggregate, append, and SyncInTxn projection share one database transaction. Balances read their own writes after commit.

Integrity without ceremony

Per-stream hash chains detect tampering. Optimistic concurrency and tenant-scoped unique keys protect concurrent appends.

Finance defaults on

Decimal money, command audit metadata, idempotency keys, AuthZ hooks, and shared-schema tenancy are first-class.

Pure library shape

No bundled HTTP server. Embed go-sequent in your process, wire PostgreSQL or memory stores, and keep ops in the CLI.

The core loop

01

Command

Capture intent with typed commands and finance-oriented Meta.

02

Aggregate

Decide and Apply events. State changes only in appliers.

03

EventStore

Append with OCC, unique keys, and a per-aggregate hash chain.

04

Projector

Update ledger views inside the same write transaction.

Locked defaults for finance

Product decisions stay explicit so operators and auditors can reason about the system without hunting folklore.

  • Ledger projections run SyncInTxn
  • Money is decimal — never float64
  • Integrity via hash chain; no event signatures
  • Shared schema + tenant_id
  • GDPR path: tombstone only
  • Outbox via PostgreSQL table polling
demo, err := bank.NewDemoApp(bank.DemoOptions{
  StoreFactory: memoryFactory,
  Idempotency:  idempotency.NewMemory,
  Authorizer:   bank.AllowBankCommands,
  EnableTenancyMW: true,
})
ctx = tenancy.WithTenant(ctx, tenantID)
err = demo.App.Service.Execute(ctx, &bank.OpenAccount{ /* … */ })
bal, _ := demo.Projector.Balance(accountID)

Start with the bank example

In-memory demo for always-on tests; Postgres sample for production-shaped balances.