9 September 2014
CQRS is a state of mind rather than a cookie-cutter design pattern
Command Query Responsibility Separation (CQRS) is based on the simple notion that you use a different model to update information than the one to read it. Although this idea has a number of significant implications for system architecture, it should not necessarily give rise to the same solution every time.
Why use CQRS?
One major benefit of CQRS is that it helps you to deal with complexity. Modern applications tend to demand different combinations of data that can be difficult to represent through a single resource. Attempting to use a single model for reading and updating data can give rise to something extremely complex that serves neither purpose well. It becomes more convenient to split the model into optimised resources.
CQRS also promotes easier scalability for high-performance applications as it gives you greater freedom over technology choices. You can optimise the performance and availability of queries while focusing on providing more resilient data processing for all your updates. Both of these can involve very different technical implementations.
CQRS is not a cookie-cutter solution and there is room for considerable variation here. It certainly isn’t a “top-level pattern” in that it should not be implemented as a system-wide architecture. Each individual area of a system (or bounded contexts in DDD speak) should be modelled separately according to its specific requirements.
In some cases it may be that CQRS is not a good fit and you’ll be better off using a simpler technique such as database replication to split your queries from data updates. Unless CQRS can address the core problems of complexity and scalability then it should not be adopted.
Patterns associated with CQRS
CQRS tends to be associated with a range of other patterns and techniques but this does not mean that it is bound to use them. Some patterns tend to follow on logically from the separation of commands and queries, but they should be regarded as a menu of possibilities rather than anything proscriptive.
For example, given that we are no longer interacting with a single resource via CRUD we can adopt task-related commands to make it easier to capture the intent of a user. Changes can be packaged up into finely grained units of work, such as updating a customer name or granting them preferred status. This gives rise to a more meaningful task-based user interface rather than something derived from the rigid definitions of CRUD.
We may wish to run completely separate infrastructures for queries and commands, implying that you will duplicate and distribute data. For example, all your query data might sit in a high-performance cache such as Redis or Memcached while commands are executed against a relational database such as SQL Server acting as the authority data store.
If we are running separate infrastructure for queries and commands then we will need a mechanism to ensure they are stay in sync. One approach can involve ensuring that a component issues an event message in response to a change in data which can be picked up by other components. This can give rise to an event-driven architecture which promotes the production, detection and consumption of events.
If you are relying on events to keep track of entities then you may not want the overhead of transforming the data and saving it to a local data store. It could make sense to use event sourcing where you store a history of the events and use them to work out the current state of the entities.
However you choose to store and interpret the events you will have to accept that they are part of an asynchronous update process. Eric Brewer’s CAP Theorem suggested that you can only provide two out of three guarantees of availability, consistency and tolerance to network partitions. If distribution is a given and availability a must-have, then you have to accept that your data will only be eventually consistent.
All these patterns are associated with CQRS, but none of them are mandatory. You could even separate queries and commands within a simple, two-tier architecture if that is the most practical solution. The point is that CQRS is actually quite a trivial pattern on its own. Where things get interesting is the implications it has for the wider architecture and the decisions that have to be made around it.