0.0
No release in over 3 years
A versatile CQRS and event sourcing framework
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 1.1
~> 0.1.0
~> 1.8
~> 1.0
 Project Readme

synapse

CI

A Go event sourcing and CQRS toolkit. Composable primitives — aggregates, events, repositories, projections — plus the operational surface real services need: a Postgres backend, broker delivery for projections, sagas with timeouts, dead-letter handling, crypto-shredding, OpenTelemetry hooks, and an admin RPC for dump/restore.

Zero third-party dependencies in the root module. Anything that pulls in an external library (a SQL driver, a broker client, gRPC) lives as a sibling module so you opt in to what you use.

Status

Pre-1.0. The public surface is still moving; expect minor breaking changes between tagged versions. The Postgres and SQLite stacks (events + snapshots + checkpoints + dead-letter + timeouts + keystore, one pool / one file) are shaping up production-ready but have not yet seen production traffic.

Install

go get github.com/ianunruh/synapse

Requires Go 1.26 or newer.

Quick look

package main

import (
    "context"

    jsoncodec "github.com/ianunruh/synapse/codec/json"
    "github.com/ianunruh/synapse/es"
    "github.com/ianunruh/synapse/eventstore/memory"
)

// Define an aggregate by embedding *es.AggregateBase.

type Counter struct {
    *es.AggregateBase
    Value int
}

func NewCounter(id es.StreamID) *Counter {
    return &Counter{AggregateBase: es.NewAggregateBase(id)}
}

type CounterIncremented struct {
    By int `json:"by"`
}

func (c *Counter) Apply(env es.Envelope) {
    if inc, ok := env.Payload.(CounterIncremented); ok {
        c.Value += inc.By
    }
}

func (c *Counter) Increment(by int) {
    c.Record("counter.incremented", CounterIncremented{By: by}, c.Apply)
}

func main() {
    ctx := context.Background()

    reg := es.NewRegistry()
    es.Register(reg, "counter.incremented", jsoncodec.For[CounterIncremented]())

    repo := es.NewRepository(memory.New(), reg, NewCounter)

    c := NewCounter("counter/hits")
    c.Increment(2)
    c.Increment(3)
    _ = repo.Save(ctx, c)

    loaded, _ := repo.Load(ctx, "counter/hits")
    _ = loaded.Value // == 5
}

A full walkthrough is in docs/getting-started.md. Runnable examples live under examples/.

What's in the box

Beyond the core aggregate/repository/projection trio, the toolkit ships:

  • Storage backends — Postgres (ADR-0024) and SQLite (ADR-0017) for the event store, snapshot store, checkpoint store, dead-letter store, timeout store, and key store. Postgres uses a shared LISTEN/NOTIFY (ADR-0025) for live-tail; read-replica routing is opt-in (ADR-0038).
  • Commands — Typed es.Handler[C, A] + es.Execute (ADR-0009) for in-process callers; es/commandbus for dispatching named, byte-encoded commands from HTTP/gRPC transports (ADR-0028).
  • Process managers and sagases/process (ADR-0032) wraps a saga as a projection that emits commands. Saga timeouts are scheduled via outbox-style intent events on the saga's own stream, atomic with Save (ADR-0043).
  • Broker deliveryoutbox.Publisher (ADR-0042) + a NATS JetStream adapter; outbox/nats.Consumer (ADR-0044) lets projections subscribe to NATS instead of polling Postgres. End-to-end demo in examples/nats-postgres-projection.
  • Dead-letter queueprojection.WithDeadLetter (ADR-0041) routes failed events to a persistent DLQ; the runner logs at Warn and continues. Admin RPCs and CLI subcommands surface entries for operators.
  • Crypto-shredding — Per-subject AES-256-GCM via a KeyStore interface (ADR-0036). Shredding a key tombstones every event for that subject in place; the rest of the log keeps replaying.
  • Observabilityslog everywhere by default (ADR-0015); an opt-in otel sibling module emits spans + duration histograms around every persistence call (ADR-0034).
  • Admin RPCssynapse-admin and a gRPC service expose stream/checkpoint/snapshot/deadletter inspection plus dump/restore in JSONL (ADR-0033, ADR-0040).
  • Liveness/readiness probessynapse/health aggregates user-registered checks behind two HTTP handlers; stdlib only (ADR-0039).
  • Event upcasters — Old event shapes evolve forward; Apply only ever sees the latest version (ADR-0023).

Documentation

Packages

The repo is a Go workspace. Core interfaces live in es; backends and contract suites are sibling packages — or sibling modules when they pull in third-party deps.

Package Module What it is
Core
es root Aggregate, Repository, Event, Envelope, Source, Snapshotter, Projection, codec Registry, error types
es/middleware root Built-in repository middleware: PerAggregateLocking, Retry
es/projection root Runner for read-model projections (type filters, batched checkpoints, dead-letter routing)
es/commandbus root Transport-facing CommandBus + middleware (Logging, Recover, Timeout)
es/process root Process managers / sagas with timeouts (intent events, TimeoutManager)
idgen root UUIDv7 identifier generator
health root Liveness + readiness HTTP probes
crypto root Per-subject AES-256-GCM crypto-shredding (stdlib only)
Codecs
codec/json root JSON event/snapshot codec
codec/proto sibling Protobuf event/snapshot codec
codec/codectest root Codec contract test suite
Event store
eventstore/memory root In-memory event store
eventstore/postgres sibling Postgres event store (pgxpool, shared LISTEN/NOTIFY, read-replica routing)
eventstore/sqlite sibling SQLite event store
eventstore/eventstoretest root Backend contract test suite
Snapshot store
snapshotstore/memory root In-memory snapshot store
snapshotstore/postgres sibling Postgres snapshot store
snapshotstore/sqlite sibling SQLite snapshot store
snapshotstore/snapshotstoretest root Backend contract test suite
Checkpoint store
checkpointstore/memory root In-memory checkpoint store
checkpointstore/postgres sibling Postgres checkpoint store
checkpointstore/sqlite sibling SQLite checkpoint store
checkpointstore/checkpointstoretest root Backend contract test suite
Dead-letter store
deadletterstore/memory root In-memory dead-letter store
deadletterstore/postgres sibling Postgres dead-letter store
deadletterstore/sqlite sibling SQLite dead-letter store
deadletterstore/deadletterstoretest root Backend contract test suite
Timeout store (saga deadlines)
timeoutstore/memory root In-memory timeout store
timeoutstore/postgres sibling Postgres timeout store
timeoutstore/sqlite sibling SQLite timeout store
timeoutstore/timeoutstoretest root Backend contract test suite
Key store (for crypto-shredding)
keystore/memory root In-memory key store
keystore/postgres sibling Postgres key store
keystore/sqlite sibling SQLite key store
crypto/keystoretest root Backend contract test suite
Outbox
outbox root Publisher interface, AsProjection, Retrying
outbox/nats sibling NATS JetStream Publisher + Consumer adapter
Observability
otel sibling OpenTelemetry store wrappers + projection.Runner tracing
Admin / ops
admin sibling gRPC admin service + synapse-admin CLI (dump/restore, deadletter, etc.)
pgtest sibling Postgres testing harness (testcontainers-go)
Examples
examples/counter root In-memory event sourcing walkthrough
examples/order root Multi-stage aggregate with command validation
examples/projection root Projection runner walkthrough
examples/process root Process manager driving a saga
examples/saga-timeout root Saga deadlines via outbox-style intent events
examples/persistent sibling SQLite-backed end-to-end demo
examples/postgres sibling Postgres-backed end-to-end demo
examples/http-service sibling CommandBus driven by net/http, live projection
examples/nats-postgres-projection sibling Postgres event store + NATS broker + Postgres read-model table

Sibling modules each have their own go.mod. A go.work file at the repo root ties them together for local development. The root module has zero third-party deps; the SQLite backends transitively pull in modernc.org/sqlite (pure Go, no CGo).

Design principles

Recorded as Architecture Decision Records (starting at ADR-0001):

  • Go 1.26 toolchain, language features and stdlib used to current capability.
  • Zero third-party deps in the root module. Backends that need them live as sibling Go modules.
  • Modernization-clean. gopls modernize ./... exits 0 in every module.
  • Serialization-agnostic core. Codecs are registered per event type; the es package never imports a specific codec.
  • Type safety and performance are co-equal goals. Where they point the same direction (most cases), take both. Where they conflict, prefer the perf-friendly option in hot paths and document the trade-off.
  • Admin RPCs and a web UI, when they exist, will be optional sibling subpackages users opt into.

License

Apache 2.0. See LICENSE.