Reference
Locked decisions
The design decisions that fix go-sequent's behaviour: SyncInTxn ledger projections, decimal money, hash-chain integrity, shared-schema tenancy, tombstone GDPR, Postgres outbox, pure library and MIT licensing.
Locked decisions
These choices were settled before implementation and the code follows them. The rest of the documentation assumes them. If you need something that contradicts one, go-sequent is probably the wrong tool, or you will be maintaining a fork.
The ten decisions
| # | Decision | Meaning |
|---|---|---|
| Q1 | Ledger projections run in SyncInTxn |
Balances update in the write transaction |
| Q2 | Money is decimal | types.Money, and float64 is banned |
| Q3 | Integrity is a hash chain | Per aggregate; no event signatures |
| Q4 | Tenancy is shared schema plus tenant_id |
No schema-per-tenant or database-per-tenant |
| Q5 | GDPR is tombstone only | Not crypto-shredding |
| Q6 | The outbox is Postgres table polling | No LISTEN/NOTIFY or broker in the framework |
| Q7 | Pure library | No HTTP or gRPC in the repository |
| Q8 | Snapshots are JSON | Versioned independently |
| Q9 | Never import Ruby Sequent databases | Conceptual inspiration only |
| Q10 | Open source under the MIT license | Public repository and documentation |
Q1: SyncInTxn for ledger projections
Projectors and workflows run inside the write transaction. A failure rolls everything back. The result is that balances never lag the event that caused them, and a client reads its own write.
The cost is that projector work counts against write latency, and a slow or broken projector blocks writes. Keep ledger projectors small and deterministic. Asynchronous delivery exists through the outbox, for non-ledger paths. See Publisher.
Q2: Decimal money
Money is a decimal amount with a currency, serialized as a string. The framework does not use integer minor units. float64 is banned in money fields, and testkit.BanFloat64Money and types.AssertNoFloatMoney check for it. Rounding policy belongs to your domain, and historical amounts are never recomputed from floats. See Money.
Q3: Hash chain, no signatures
Each aggregate stream has a SHA-256 chain over canonical event fields. Tampering is detectable on load or with VerifyChain. Event signatures are out of scope. Database access control, encrypted backups and optional WORM storage are complementary controls at the operations layer. See EventStore and Threat model.
Q4: Shared schema and tenant_id
One set of tables. Every command, aggregate, idempotency row and projection carries a tenant. Unique-key scopes include the tenant. partition_key can map to tenant for physical partitioning inside the same schema. Row-level security is optional and left to you. See Tenancy and Partitioning.
Q5: Tombstone-only GDPR
To honour an erasure request, emit a tombstone event such as gdpr.SubjectTombstoned and have projectors scrub or stop serving the personal data. Events stay immutable. Crypto-shredding is not provided. Physical deletion of event rows is an exceptional operation that needs stream-repair tooling and breaks the hash chain unless handled deliberately. The optional PayloadProtector supports field encryption of PII without implying anything about erasure or authenticity.
Q6: Postgres outbox polling
When you use an outbox, messages are rows in a Postgres table written in the write transaction and drained by polling with Claim, Ack and Nack. Delivery is at-least-once, and consumers must be idempotent. See Publisher.
Q7: Pure library
go-sequent ships no HTTP server, no gRPC stubs, no UI and no ORM. The CLI is an ops tool for migrations, replay schema stages and outbox polling. You embed command.Service in your own process.
Q8: JSON snapshots
Snapshot payloads are JSON with a SnapshotVersion that you bump on incompatible changes. See Snapshotting.
Q9: Never import Ruby Sequent databases
The conceptual model comes from Sequent. The schema, serialization and APIs do not. There is no dump or wire compatibility, and none is planned. Upcasters exist for your own event history only. See vs Sequent (Ruby).
Q10: Open source, MIT license
The project is open source under the MIT license, and the documentation is public. Contributions and issues are welcome at github.com/go-sequent/go-sequent.
Baseline decisions (D1 to D10)
| ID | Decision |
|---|---|
| D1 | Module path is github.com/go-sequent/go-sequent |
| D2 | PostgreSQL is the first and only production event store backend |
| D3 | Default publish mode is sync projectors in the write transaction |
| D4 | The UnitOfWork is context-scoped, not process-global |
| D5 | JSON is the default codec for commands and events |
| D6 | Compensating events over mutable history for business corrections |
| D7 | float64 banned in money fields; money type is decimal |
| D8 | Explicit handler registration, with no mandatory reflection auto-registration |
| D9 | Design before implementation |
| D10 | Conceptual Sequent inspiration only, never a Ruby Sequent database import |
Principles behind them
- Events are the source of truth. Projections are disposable.
- Aggregates do not read other aggregates’ state while handling a command.
- A command, its events and its synchronous projections share one database transaction. Asynchronous modes are opt-in.
- Finance defaults are on: decimal money, idempotency keys, audit metadata on every command.
- Use plain Go: interfaces, constructors,
context.Context, and no global mutable singleton.
Related
Docs track the go-sequent library. When APIs drift, the source wins.