What is CQRS?
July 6, 2016
CQRS is short for the command query responsibility separation pattern. This post traces the history of this pattern, the benefits, and discusses two common stumbling blocks: error handling and data validation.
The processing of commands and queries is fundamentally asymmetrical …
A Command modifies state.
A command does something but does not return a result. Eiffel: a language for software engineering (PDF, p. 22)
A Query reports the current state.
A query returns a result but does not change the state. Ibid
Command methods go in one object; query methods in another.
This is the bit Greg Young added to create CQRS.
Otherwise, this pattern is the same as Bertrand Meyer’s Command and Query Separation principal, which
states that every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, Asking a question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.
So what’s the big deal?
In my experience, the database becomes the tight resource in a big system. This CQRS pattern allows you to separate the write data model from the read data model, and this means:
- your write model could insert into an event log table with minimal indexing, that is partioned by date, and
- your read model could be a set of non-normalized projections that match how data is shown in the user interface.
Decoupling the read and write models forces you to figure out how to feed the read side from the write side. The most common approach is to use events; every command emits one or more events. The read model subscribes to those events and updates their state. (Note that this event sourcing is not required as part of CQRS.)
Using events to integrate the read and write models has some nice properties:
- you can add a new projection at a later date and replay all events through that projection,
- if you have a logic error in an existing projection, you can fix it and replay all events,
- since the read-model projections match the user interface, queries are very fast,
- you can scale read model and write model independently,
- if you encounter a bug you know exactly what events triggered the error condition and can replay them in a testing environment.
How to you ensure uniqueness?
In CQRS, the command processors do not access read models. (They are split. Completely.) This leads to a a common question: how do to validate the command data?
The problem I’ve come to now is validation. Every post have a shortUrl, and the shortUrl should be unique, but where should I put this validation in the domain?
I have seen two different answers.
- Accept eventual consistency and deal with this manually. Eventual Consistency and Set Validation (Greg Young, 2010) It is low cost and low frequency and it is cheaper to just send an email and fix it manually.
- The command processor should store enough state that it can ensure business rules are met. CQRS/Event Sourcing, how get consistent data to apply business rules? (Chris Baxter, 2012) In this specific instance, the command processor should keep a list of all current short urls. Access to that list should be synchronized.
The second answer is from Domain Driven Design, and is my preferred approach. I should be so blessed to have written a system so popular that the first approach is required!
How to you handle errors?
If the data sent by a command is invalid, and a command cannot return a value, how do you report an error back to the client?
The general consensus is to raise an exception. CQRS FAQ: Testing (Edument)
To me, raising an exception performs the same function as returning a value, which breaks Meyer’s rule. Which leaves two choices:
- return a value and break Meyer’s command rule, or
- don’t return a value and expose a query that retrieves the error state.
I’ll need to play around with this when I start building something real …
Aug. 17, 2016
- Add lede.
- Add my preference for dealing with uniqueness by keeping enough state in the command processor.
- Expand discussion of error handling.