Event Sourcing & CQRS
Understand Event Sourcing and CQRS patterns, focusing on state reconstruction, snapshots, separation of read/write pathways, and eventual consistency.
The Concept
In traditional database designs, applications store the current state of domain objects directly in relational tables. For example, a user's bank account is represented by a single database row containing a balance column. When a transaction occurs, the application updates that column. However, this approach discards the historical context: you know the current state, but you have lost the historical timeline of updates that led to that state.
Event Sourcing resolves this limitation by defining application state entirely as an append-only log of immutable domain events. Instead of storing the state value itself, the database stores the record of changes (such as AccountOpened, MoneyDeposited, or MoneyWithdrawn).
To read or update this system efficiently, engineers combine Event Sourcing with CQRS (Command Query Responsibility Segregation), separating the write model pathways from the read model pathways.
Practical Analogy
Consider the difference between a simple score counter and a full accounting general ledger:
- Traditional CRUD databases are like a scoreboard at a basketball game. When a team scores two points, the referee updates the board from 80 to 82. The scoreboard only tracks the current score. It has no memory of who scored the points, when they were scored, or if a scoring mistake was made and corrected.
- Event Sourcing is like the official scorekeeper's ledger. It lists every event: Player A scores 2 points (Version 1), Player B blocks a shot (Version 2), Player C scores 3 points (Version 3). You can calculate the final score (the state) at any time by adding up all the scoring events in the ledger. This preserves a perfect audit log.
Aggregate State Reconstruction
In Event Sourcing, the core business entities are modeled as Domain-Driven Design (DDD) aggregate roots. When an application processes a new command (for example, withdrawing money), it cannot trust raw memory: it must first load the aggregate root's current state to validate business rules (e.g. ensuring the balance does not drop below zero).
The application reconstructs state using a process called hydration:
- The application queries the event database for all events associated with the aggregate ID.
- The application instantiates a blank aggregate object.
- The application iterates through the historical events in sequential order, applying each event's state changes to the aggregate object.
- Once the last event is applied, the aggregate is fully hydrated and represents the exact current state.
Snapshots for Performance Optimization
As an system runs, an aggregate root can accumulate thousands of events. Replaying all these events on every single write operation introduces significant CPU and database latency.
To optimize hydration, systems implement snapshots:
- Every X events (for example, every 100 events), the system serializes the hydrated aggregate state and writes it to a dedicated snapshot store.
- During the next hydration, the application reads the latest snapshot.
- The application then queries and replays only the events that occurred after the snapshot's version, reducing the hydration cost from
O(N)events toO(1)operations.
Separating Read and Write Paths (CQRS)
Because the event store is optimized for high-speed sequential append-only writes, querying it directly for application views (such as list filters or search fields) is extremely complex. This is where CQRS is applied. CQRS splits the application into two distinct execution pathways:
- Command Path (Writes): Focuses exclusively on business operations and state updates. It validates commands, executes domain logic on the aggregate, and writes new events to the event store. It does not return query data, only success or failure.
- Query Path (Reads): Focuses exclusively on data retrieval and visualization. It queries read-optimized database views (such as a denormalized SQL table or an Elasticsearch index). It never modifies data.
Synchronization Pipelines and Projectors
To keep the read models in sync with the event store, systems build synchronization pipelines. As the event store appends new events, it publishes them to an event broker.
A consumer process, known as a projector or event handler, listens to the event stream. The projector reads each incoming event, updates the corresponding read-optimized database tables (e.g., updating a customer address table when an AddressChanged event is read), and commits the change.
Consistency Realities and UI Design
Because read model updates happen asynchronously after the event store write is complete, systems built on Event Sourcing and CQRS are eventually consistent. There is a small latency window between the event being appended to the write log and the projector updating the read database.
This latency requires engineers to adapt their user interface designs:
- Optimistic UI Updates: The frontend immediately updates the UI layout assuming success when a user submits a command, rather than waiting for the read model projection to sync.
- Command Validation Feedback: The command API returns validation updates immediately, letting the user know the command was accepted, while the background workers handle final database alignment.
Event Schema Evolution and Upcasting
Since event logs are immutable, they cannot be updated or restructured when application requirements change. If a developer alters an event schema (for example, splitting a Name string property into separate FirstName and LastName properties), legacy events in the database will crash modern deserializers.
To handle schema evolution, applications implement two patterns:
- Upcasting: An upcaster is a middleware component that intercepts old events during the database read operation before they reach the aggregate. The upcaster dynamically transforms the old JSON structure into the new schema format on the fly, avoiding data migrations.
- Event Migration: If upcasting introduces too much runtime overhead, background migrations copy, transform, and rewrite historical events to a new version of the event log database.
Further Reading
- Martin Fowler: Event Sourcing — The foundational article defining the patterns and trade-offs of event-driven architectures.
- CQRS Documents by Greg Young — Architectural whitepapers detailing the segregation of read and write responsibilities.
- Microsoft Architecture Guide: CQRS Pattern — Best practices for implementing eventually consistent query models in cloud systems.
Prerequisites
Code Examples
Core Literature References
Event Sourcing
by Martin Fowler — Patterns of Enterprise Application Architecture / Event Sourcing
View sourceDomain-Driven Design: Tackling Complexity in the Heart of Software
by Eric Evans — Chapter 6: The Life Cycle of a Domain Object
View sourceContinue learning
ACID & Isolation Levels
Deep dive into database transaction guarantees, isolation levels, concurrency anomalies like write skew, and control mechanisms such as MVCC, 2PL, and SSI.
API Gateways
Understand the API Gateway pattern as the central ingress point for microservices, handling routing, auth, rate limiting, and protocol translation.
API Security & OAuth 2.0
Understand API authentication and authorization mechanisms, JWT security, and the OAuth 2.0 framework including Authorization Code Flow with PKCE.