On a recent Improving Talks presentation on Designing the Modern Distributed System, I went over the five Cs of such design. Specifically, those were:
Today I’d like to delve a little more into the contracts point and discuss why I so strongly advocate for the use of the CQRS pattern in the implementation of modern distributed systems.
The CQRS pattern has existed for many years, and Martin Fowler attributes its creation to Greg Young. At its core, the pattern seeks to recognize that in the spectrum of data operations:
Read is not co-equal with the other operations. A properly implemented read operation is complete without change to the state of the system. Meanwhile, the other three operations modify the system state, the data stored within the system.
It is from this core observation, that the CQRS Pattern evolves. In CQRS we now seek to identify Read operations as queries and all other operations (create, update, delete) as commands. This allows us to more clearly articulate commands that modify the state of the system, and queries do not.
With this difference identified we more to our next point of CQRS, a great deal of freedom begins to evolve. Our models used to update no longer need to precisely represent the models returned by our Queries. As that move happens, we begin to see the responsibility segregation which that pattern espouses. If commands and queries are not going to share models, why then should they be handled by the same pieces of the system at all?
The Power of Read Projections
Having separated query from command, our queries can easily deviate from the core read model of the system to meet the needs of the display aspects of the system. Queries which perform aggregations, or which subset data without user input are perfectly reasonable and no longer viewed as exceptions to the “canonical” representation of the system as a whole.
Now when CQRS is further combined with asynchronous communication and either an event store or message bus suddenly even more rich capabilities begin to evolve. Let us imagine we have a system that contains customer service, an order service, and an address service. We would like to look at the top five customers per zip code. This is a classic reporting request, but in a Microservice-based world, we quickly see that a complete answer to this query requires data from all three services.
How then do we handle this query? We create a read projection to support it. The steps necessary for this to succeed all align with the CQRS Pattern when combined with an event store or message bus. For this example, I will assume we are using an event store, here are the necessary steps:
- Ensure that all command operations in customer, order, and address services update the event store with the changes which occurred.
- Subscribe to our top five customers read projection to the changes occurring in all three event stores. Keep track of customers' details, total order amounts, and zip codes in this read projection.
- Query the read projection, not the three stores independently, for the response to the query as a whole.
This implementation produces a database to support the read projection which does not actually own any of the data present within. Instead, it is simply a query optimized mirror of relevant data in order to support the response to our top five Customers per zip code.
A Lot of Work
Often as I seek to teach these patterns to development teams, I hear the complaint that the implementation of this pattern is “a lot of work”. In truth, this can be true in the very short term, because you must create the patterns in your base libraries to support these implementations. You must make it very easy to ensure that the event store is updated on every successful command. You must make consumption of the event store easy for developers implementing read projections.
These changes though are at a more fundamental level a new way of thinking about things, and that is often difficult for organizations to wrap their head around. If you are approaching such an implementation, make sure you have the appropriate mentors available to your team to assist with this transition. If you don’t have those people on staff, we would be happy to assist you with that, we have experts around the globe who have implemented these patterns multiple times.
Also, as you consider such a transition, ensure you have set up ways for the team to share their knowledge with one another. A well-cultivated Lunch and Learn program, for instance, can save you far more time and money than it ever costs by sharing knowledge across the organization and making asking questions a healthy and respected thing. Learn more about our Lunch and Learns here, and feel free to reach out to us.