Go's combination of strong typing, excellent concurrency primitives, and low-overhead runtime makes it an exceptional fit for CQRS and Event Sourcing systems. The language's simplicity forces clarity in aggregate design, and goroutines provide natural building blocks for projection consumers. This guide covers a production-ready CQRS/ES implementation in Go, from event store design through projection management.
Core Architecture
A CQRS/ES system in Go decomposes into four primary components: the command handler (validates and processes commands), the event store (persists domain events), the aggregate (encapsulates domain logic), and the projection engine (builds read models from events).
Defining Domain Events
Events are immutable records of state changes. In Go, leverage interfaces and struct embedding for a clean event hierarchy.
Use integer amounts in cents to avoid floating-point precision issues in financial calculations.
Building the Event Store
The event store provides append-only persistence with optimistic concurrency control. PostgreSQL with JSONB columns is a proven choice for Go-based event stores.
The PostgreSQL schema:
Implementing the Aggregate
Aggregates encapsulate domain logic, validate commands, and emit events. The pattern in Go uses method-based command handling and a state evolution function.
Command Handler
The command handler orchestrates loading the aggregate, executing the command, and persisting new events.
Need a second opinion on your system design architecture?
I run free 30-minute strategy calls for engineering teams tackling this exact problem.
Book a Free CallBuilding Projections
Projections consume events asynchronously to build query-optimized read models. Go's goroutines make it natural to run multiple projection consumers concurrently.
Query Service
The query side reads from projections, completely decoupled from the write side.
Snapshot Support
For aggregates with long event histories, snapshots avoid replaying the entire stream.
Production Considerations
Concurrency: Use errgroup for running multiple projections concurrently. Each projection goroutine should have its own context for independent cancellation.
Serialization: Stick with encoding/json for event payloads unless you measure serialization as a bottleneck. Protocol Buffers add complexity and reduce debuggability of stored events.
Testing: Test aggregates with given-when-then style tests — given a set of historical events, when a command executes, then specific events should be emitted.
Monitoring: Export projection lag (current event store position minus projection checkpoint) as a Prometheus gauge. Alert when lag exceeds your SLA threshold.
Conclusion
Go provides an excellent foundation for CQRS and Event Sourcing implementations. The explicit error handling prevents silent failures in event processing, the type system catches event schema mismatches at compile time, and goroutines give you a natural concurrency model for projection consumers. Start with PostgreSQL as your event store — it handles surprisingly high throughput — and only move to specialized solutions like EventStoreDB or Kafka when you outgrow it.
The code patterns in this guide are designed to be copied and adapted. Every component — event store, aggregate, projection, query service — follows Go idioms and can be tested independently.