tl;dr - when should I use CQRS design pattern and stay sane.
When building an API the typical practice is to divide it into parts, simplest would be presentation, service / business logic and data access layers
There is nothing wrong with this approach but questions araise when application becomes bloated and unreadable.I prepared simple user’s microservice with basic functionality to make case-study on it. But first things first.
Must be mentioned before. It stands for Command-query Separation. The simplest explanation would be:
It separates two main sections of our application. Queries are thoose actions which doesn’t change data (in most cases) and return something whereas commands are those which change data and return nothing (again, in most cases). Most likely that you used it even without knowing it because it’s kinda natural approach to the problem. It allows us to:
- Distributed responsibility. When working in team, less experienced developers may receive queries to take care about because they don’t mess up with data.
- Clean code. However, this argument is often overused. I’ll explained it later.
Command Query Responsibility Segregation, idea presented by Greg Young and Udi Dahan. Let’s stop for a moment. In most modern frameworks we use OOP right? Yeah but where thoose objects can be seen in above diagrams? I’m not talking about singleton pattern, which creates single instance of eg. service class in the app because it serves another purpose and would be cheap answer.
And that’s the problem. Actions in classic CQS are handled by methods in the end. CQRS is handling them by objects, an instance of action class with one or more methods.
API looks as follows:
Simple REST CRUD microservice. Additionally user can login using his name or email and view history. Application has two modules with same set of routes. The ‘simple’ variant is made with classical approach and the other one with CQRS.
Let’s look into the code:
Controller uses service with those methods:
It takes only 116 lines of code so not a big deal but will arise when scaling because of no clear way to organize code.
Let’s analyze project structure.
Here we have two controllers:
As you may have noticed queries and commands in form of object. Second important aspect is that we have no service in this module. It’s functionality is distributed in queries and commands directories.
Each action has its handler. Let’s look into one:
It has same functionality as List() method in service.
As you see here we have more breathing space. Method does no longer needs to be long or cut into many messing with code organization. Simillary to main() method known from C, here execute() is beeing fired when handling query.
Note that we can’t always separate read and write blocks. Login query creates history entry violating pure Separation rule. But implementing two separate routes would be an art for art.
Domain-Driven Design elements
Project contains new class called User.
It’s object in our project domain and aggregate root. It means that we can talk about it and manipulate on it independently. Good example is basket in online shop app. There is no sense in implementing functionality for order positions because they don’t exists without basket. We don’t create data access to as well. In simpliest implementation it could be entity class from sequalize but this practise have a lot of flaws.
In this API it could be easily omitted and Domain-Driven Design is topic for another article. I mencioned it because it is present in official Nest.js CQRS documentation as well as Event Sourcing which is not part of CQRS. I’ll be demystifying thoose terms in next article.
Is CQRS worth it, at least considering example discussed in article? It depends on horizon of the project. If it’s going to remain simple and there are 1–2 programmers responsible for it there is no need to complicate matter even more.
But things change when code begins to grow without control. There is no need to decide on one approach in scope of whole project. Simple modules may still be based on simple controller-service setup whereas more complicated, especially contaning long in implementation routes should be considered as worthy CQRS candidates.
Project is available here: https://github.com/Matii96/users-service
Thanks for reading :)