CQRS and Event Sourcing for the Rest of Us
by odinellefsen | 2025-06-01 20:31:39+00:00
Many teams love the idea of an immutable event log yet never adopt it because classic Event Sourcing demand aggregates, per-entity streams, and deep Domain-Driven Design. Each write often means replaying thousands of events to rebuild an aggregate in memory before a new event can be appended. That guarantees perfect consistency, but it also raises the cost of entry.

In Domain Driven Development + Event Sourcing you design an Aggregate, for example Order. For the Aggregate you design Domain Events like OrderCreated, OrderInfoUpdated, OrderArchived, and OrderCompleted. This means that every Event stored for the Order aggregate is one of those designed Domain Events. At this point you create instances of the Order aggregate (one instance for each actual product order in the system). And this looks like Order-001, Order-002, and so on. For each instance, for example, Order-001, you append Domain Events corresponding to what has happened to that order in that orders event stream.

You have to make sure that a user action is valid before you append a Domain Event to the event stream (which is your source-of-truth). Validating a user-action/Command is done by rehydrating/replaying every past event for the aggregate instance in question. For an aggregate called BankAccount with it’s aggregate instances, i.e. BankAccount-1234, there can be millions of Domain Events/events which can take a long time to rehydrate/replay every time a person does an action on their bank account where you have to validate the action, which is where a concept called snapshots comes in to make this faster.

The point of rehydrating the entire event history is because you want to recreate the current state your application or more specifically the current state of the entity/aggregate-instance, i.e. BankAccount or Order. You do this to be confident that you’re validating a new user action against the latest application state and not an old application state.

There is another approach to achieve validation (and achieve the core concept of event sourcing) that doesn’t require you to handle the complexity of rehydrating your entire event stream nor designing aggregates just to be able to validate a new user action. This alternative that I’m going to explain lowers the barrier to entry for CQRS + Event Sourcing because it removes DDD design complexity, and widens use-cases and accessibility significantly (some classic use-cases may not be a good fit for this approach). But at the same time it requires a different and strong infrastructure.

The approach I'm suggesting repurposes Domain Events to instead serve the function of being the stream of events what we call Event Types. Instead of having event streams for each individual order you’d group every created, updated, archived, or completed order in it’s respective Event Type. This means that for the provided example you’d have 4 event streams for the Order aggregate instead of having an event stream for every order in your system.

How I achieving Event Sourcing is by doing simple SQL business logic checks against real time Read Models. These contain the latest state of my application with a lag, in high-throughput critical situations, of single digit milliseconds, and in less critical smaller throughput situations, single digit seconds.

Both approaches use the current state of your application, either by calling the read model or by rehydrating all past events to recreate the current state. Rehydration really matters only when an out-of-sync Read Model is unacceptable. The production database is a downstream service in CQRS, so a slight delay always exists. In high-contention or ultra-low-latency domains such as real-money transfers you should replay a single account stream to avoid risk. If the Read Model is updated within a few milliseconds to a few seconds then validating against it is completely sufficient for the vast majority of applications.