Event Sourcing and CQRS Explained
Traditional applications store the current state of an entity. If a user changes their address, the old address is overwritten. In systems where auditability and history are critical (like banking or e-commerce), this destructive update is unacceptable. Enter Event Sourcing and CQRS.
What is Event Sourcing?
Event Sourcing mandates that instead of storing the current state of an object, you store a sequence of immutable Events that led to that state. The current state is derived by replaying the events.
For a bank account, instead of storing: { balance: 50 }, you store the event stream:
AccountCreated { id: 1 }MoneyDeposited { amount: 100 }MoneyWithdrawn { amount: 50 }
Benefits of Event Sourcing
- Perfect Audit Trail: You never lose data. You can reconstruct the system's state at any point in history.
- Temporal Queries: You can easily answer questions like, "What was the user's account balance last Tuesday at 3 PM?"
- Bug Recovery: If you discover a bug in how balances are calculated, you can fix the logic and replay the events from the beginning of time to fix the database.
The Problem: Querying Events is Slow
If you need to show a user their current balance, fetching 10,000 transactions and summing them up on the fly is terribly inefficient. This is where CQRS comes in.
Command Query Responsibility Segregation (CQRS)
CQRS states that the data model used to update information (Commands) should be strictly separated from the data model used to read information (Queries).
How They Work Together
- Command Side (Write): Receives an action (e.g.,
WithdrawMoneyCommand). It validates the business logic, appends aMoneyWithdrawnEventto the Event Store (usually Kafka, EventStoreDB, or PostgreSQL), and returns successfully. - Event Handlers: Background workers listen to the Event Store. When they see a
MoneyWithdrawnEvent, they update a separate "Read Model". - Query Side (Read): When the UI requests the user's balance, it queries the Read Model (which could be a highly optimized Redis cache or Elasticsearch index). It does not touch the Event Store.
The Trade-off: Eventual Consistency
Because the Read Model is updated asynchronously, there is a delay (usually milliseconds) between a command succeeding and the read model reflecting the change. This is known as Eventual Consistency. Your UI must be designed to handle this (e.g., showing optimistic UI updates or using WebSockets to notify the client when the read model is ready).
Conclusion
Event Sourcing and CQRS introduce significant complexity. You have to manage event versioning, eventual consistency, and complex infrastructure. However, for core domains where data history is a business asset (financial ledgers, shopping carts, legal documents), it is the most robust architectural pattern available.