CQRS Workshop – Retrospective
Today I had the honor of leading the first of a series of workshops at ClearMeasure. The topic of this workshop was CQRS. After a brief introduction into the topic the attendees were working in teams of 2 to 3 members on a problem that I defined. The goal was to implement a Task Management System that was defined by the following business requirements
- A supervisor can define/schedule tasks for certain members of her team
- Tasks have a name, due date, instructions, a list of assigned personnel, etc.
- Once the supervisor publishes a task all assigned personnel can see it on their dashboard
- Any assigned personnel can complete a task by entering a completion date/time and a completion comment
- On my personnel tasks dashboard I see tasks sorted by due date. Overdue tasks are specially marked
- The supervisor can see the list of all her team members. She can also see filtered lists of tasks (by team member or task status, etc.)
- The supervisor can cancel or table published tasks
It was very important that CQRS would be applied in this project.
CQRS although simple in its definition is by no means a pattern that is very well understood. Most business applications out there in the wild do either not implement it at all or only partially follow its tenets. A lot of people also automatically associate CQRS with domain driven design (DDD) or event sourcing (ES) and other architectural patterns. But that is wrong. CQRS does not imply any of these other patterns. Yes, it’s true, CQRS can be used together with those patterns in a very straight forward way but it is not necessary.
All teams participating in the workshop came up with totally different ways of attacking and solving the problem. Only while deeply involved with the problem did certain questions about the implications and/or advantages of CQRS surface. It was really super interesting to observe this process. All participants were very experienced developers and still there were a lot of stumbling blocks to overcome.
Here is a summary of steps that I chose to take and that will lead me in no time to a successful implementation that applies CQRS.
1. Define the ubiquitous language
How do we get to the ubiquitous language? By talking to the domain experts, stake holders, project owner and/or future users of the application. Listen carefully what they say and how they say it. What are some of the nouns they are using over and over again and what kind of verbs are used repeatedly? If we take a look at the given business requirements we can quickly identify the nouns as they are
- team member
and accordingly the verbs
- define or schedule
Evidently we can also identify some of the properties the task entity has. These are name, due date, instructions, list of assigned personnel and probably a status property too.
2. Define the contracts
Once I have an idea how the ubiquitous language looks like and knowing the business requirements I like to define the contracts that I’m going to use in the application. For me contracts are the commands, events and end-points that are used in the application. Let’s first concentrate on the commands. Commands are usually triggered by user actions. Again looking at the business requirements we can identify the following commands.
The name of the command should clearly reflect its context and its intent thus don’t be shy and use long names where necessary. The payload of a command should only contain the bare minimum of information that is needed that the target of the command can execute it successfully. In certain circumstances this can mean that no payload at all is needed since the name of the command already implies everything. The more unnecessary data we add to the payload of a command the more confusion we might cause. Keep your commands focused. The more explicit they are the easier it is to validate them and make sure that they do not violate business constraints. As a sample take the very generic command UpdateTask versus the very specific command SetTaskDueDate. Where the former leaves you with a lot of guesswork on the users intent the latter is crystal clear.
Now let’s look at the queries. Again the name of the query should identify the context and intent of the user or application. In our sample application we could identify the following queries
My preferred backend API nowadays is a REST-ful API. And when I say REST-ful then I imply that my API might not be pure in the sense of REST, specifically when it comes to write operations. That said, given the above commands I define an endpoint for each distinct command, i.e.
Note that I always use the verb POST for my commands. I never use PUT and hardly ever DELETE. The latter two are OK when used in CRUD style application. My preferred data exchange format is JSON, thus in the HTTP header we will have content-type application/json. If you are using ASP.NET Web API then attribute routing makes it super easy to implement the above endpoints. The similar is true when using Node JS to implement the backend.
For the queries we can again define an endpoint per query similar to this
3. Decide which architecture to use
Since CQRS can be used in many situations there is not only one overall architecture to select from. Here I will present a high level view of 3 different types of backend architectures. They all have in common that the write operations are clearly separated from the read operations. There is no coupling between the two concerns.
Main stream architecture
Here is a high level view of an application that uses CQRS in conjunction with DDD
and finally here is a high level view of an application that uses CQRS, DDD and event sourcing all together
Only now I start to code… If I spend more time analyzing, planning and designing then I am spending less time coding and re-coding.
So, what does CQRS now mean when I’m down to the code? This means that nothing of my domain model or write model can be used when querying data. Forget about DRY for a moment. Even if you think that your Task class in the domain model contains all the properties that you need when querying data you cannot use it! Define a separate DTO for this purpose instead. You might call it TaskDto or TaskInfo. You will notice that even if at the beginning the Task entity of the domain model and the TaskDto have exactly the same properties they will diverge over time as new requirements surface. You don’t want to couple your write and read models at any time.
If you’re using an ORM on the write side then avoid using it on the read side as it only adds unnecessary overhead. If you’re coding on the .NET platform then you might use ADO.NET directly or a lightweight library like Dapper instead. The situation is even simpler if you’re using e.g. a document DB to contain your read model since it eliminates the impedance mismatch between code and data store.
Workshops are a very good way to get people involved and keep them active. The motto is learning by doing. A presentation where a well prepared speaker talks about a certain topic might be very enlightening but only a workshop can make every single participant think about and practice the topic at hand. Here at ClearMeasure the participants were involved in very intense discussions all the time. Whiteboards were used and pair programming was practiced. Since the teams were formed ad hoc there was also a lot else going on which was interesting to observe. Team dynamics, time management, role assignment, leadership, etc. to name just a few.
Hopefully I got you excited and you’ll want to participate at the next workshop too! If you do not live in the Austin TX area this is not a problem since we also include remote participants. During the workshops we are connected via Slack, Zoom or Goto Meeting, Skype, Hangout, etc. Please follow us on Meetup.