Dispelling the eventual consistency FUD, using event sourcing

So your organization is toying with the idea of using Event Sourcing. The benefits look promising, but you can’t seem to shake off the nagging doubt of that one aspect of adoption - Eventual Consistency because of its alignment with CQRS. Your organization’s systems have always been following the principle of Solid Transactional Consistency (maybe you are in the Banking Business), and CQRS/ES becomes a big no-no because of just this one aspect. In addition, everyone within your organization finds those examples of order items eventually appearing in your cart extremely amusing. Finally, something to agree upon on that Friday Zoom bonding call!

In the CQRS/event sourcing space, we are equally guilty of not countering the FUD with enough examples and literature that gives relevant data points for folks wanting to adopt these patterns. What if I told you that your systems built with CQRS/ES are as transactionally consistent as systems built without these patterns using the more formal storage methods!

Well, let’s walk through an example actually to prove it. Before we get into it, a quick 30,000 feet view of these patterns - CQRS helps you split your Domain Model into two distinct partitions, the Command Side for processing “Commands,” which result in a state change, and the Query Side for processing “Queries” to retrieve state. Event Sourcing proposes the storage of the state of your application as a sequence of immutable events in an event log. State changes are validated directly against that event log, while Queries are executed against projections derived from that log. This allows the event log to be considered a reliable source of truth.

We will probably take the most commonly used example of Money Withdrawals to demonstrate this. But, first, we need to consider the only invariant that the customer has the required balance in their bank accounts to perform a withdrawal. For example, a customer might make multiple withdrawals in a short time frame, and at the end of each withdrawal, the balance needs to be in a consistent state. Else you could have customers make more withdrawals than allowed, and the bank shuts shop.

 Some customers on the left send several withrawal/deposit requests (represented by arrows) to the ATM Banking portal (represented on the right)

Traditionally, you deal with the consistency problem using a database with a single mutable record representing the account balance. Every withdrawal operation loads the balance record, checks the withdrawal amount against the current balance, and accordingly, the system decides whether to permit the withdrawal or not. An important point to note here is that before the customer intends to make a withdrawal, they are presented with a view of their current balance. This View is constructed by loading the same mutable Account Balance record.

Essentially you use the same model:

  • To display the balance, which helps the customer to make an intent to make a withdrawal.
  • To help the system decide on whether the intention to make a withdrawal is permitted or not.

This approach gives you the perceived benefit of guaranteed “read” consistency. However, while the information is presented on screen, there is no guarantee that the data isn't modified in the meantime. Thus, the user's decision is always made on stale data. In addition to that, you have to deal with problems around scalability, availability, and model complexity. CQRS/ES offers a great way to help address these kinds of problems with strong consistency guarantees.

How exactly is that achieved? Going back to our original example, say we now have decided to adopt Event Sourcing. So instead of a single mutable account balance record, we now store all the operations (All the withdrawals and the deposits) that happen against that account as a sequence of events. A representation on how the all sequence of events that happened are stored with Event Sourcing (retaining full context and information of what happened) while the traditional storage only keeps a Single Mutable Record

When the customer now makes the intent (i.e., a Command) to do a withdrawal transaction against a particular account, this will load the past events from the Event Store against that account (i.e., the Withdrawals/Deposits) and then replay them to arrive at the current state for the account balance. This state is then used to decide on whether to permit the withdrawal or not. In short, it is guaranteed that Command processing in an Event Sourced system will always be done against the latest and current state, not an eventual stateAn Image showing how some commands are sent. The first command is directly stored as an Event, the 2nd command triggers the load and replay of Event 1 and finally stores a second Event corresponding to the command. The 3rd command, triggers the load and replay of Events 1 and 2, then is rejected, which means that no Event is stored.

This takes us back to the View as the customer will intend to perform a withdrawal only after they are presented with the balance. In Event Sourcing-based systems, the balance (aka the balance projection) that needs to be displayed is not retrieved from the Event Store. Instead, it is retrieved from another datastore which constructs and stores the balance the way it is used in this case as a display. The construction and storage are done asynchronously by subscribing to the events emitted once it is committed to the Event Store. Essentially it might not reflect the current state, and hence we term the balance projection as eventually consistentAn Image that represents how, in a CQRS architecture the user can send commands and queries, but the query is handled by a projection that may not have yet received/processed all the Events resulting from the execution of the previous commands. That's what implies eventual consistency.

To summarize, Command Processing in Event Sourcing will always be performed against the latest state and not an eventually consistent state. However, projections used to query the state are always eventually consistent due to the asynchronous nature of its construction and storage. While the example shown here is a bit trivial to demonstrate the concept, it holds for very complex command processing. It should be noted that in any system being built, there will be islands of consistency in a river of constant change. This holds for both Strongly Consistent and Eventually Consistent systems.

Axon provides Event Processors that significantly scale up Projections' construction in almost near real-time once events have been published. In addition, it also provides Subscription Queries that listen to any updates to your Projections and delivers them in real-time to interested clients resulting in an enhanced user experience.

Vijay Nair
Architect, DDD, CQRS, and event sourcing evangelist. Vijay is author of "Practical Domain-Driven Design with Enterprise Java" and a prolific writer and presenter on DDD, CQRS, and event sourcing.
Vijay Nair