The Agile Echo

CQRS pattern: advantages of Command and Queries

I think CQRS deserves its autonomous role in Software Development and should be discussed more because it is a powerful technique to organize our software and keep it maintainable. This is what I want to do with this article: describe what the CQRS pattern is about, with some practical examples and usages, and especially highlighting its advantages on a business application and why it makes total sense to apply it on its own, no need for ES.

Cover Image for CQRS pattern: advantages of Command and Queries
Dan the Dev
Dan the Dev
technical-excellence
cqrs

Introduction

Hello, developers! 🚀

Today’s topic is one of those that made me desire to stand up for a discussion when I heard about it: the Command-Query Responsibility Segregation pattern (CQRS) is very powerful and useful but it’s usually only discussed together with Event Sourcing (ES) and this is a pity.

I think CQRS deserves its autonomous role in Software Development and should be discussed more because it is a powerful technique to organize our software and keep it maintainable.

This is what I want to do with this issue: describe what the CQRS pattern is about, with some practical examples and usages, and especially highlighting its advantages on a business application and why it makes total sense to apply it on its own, no need for ES.

From CQS to CQRS

To talk about CQRS in deep, especially to prove my point about its importance in a Software project, we need to introduce its “little brother”: the Command-Query Separation principle (CQS).

The Command-Query Separation principle states that a class can have 2 types of methods:

  • a Command: a method that can mutate the state of the system, but doesn’t return data from the state itself - a violation of this principle is considered weak and is allowed: for example, think of a method that creates a new entity and returns the ID

  • a Query: a method that returns a portion of the state of the system, but doesn’t change it in any way - no exception allowed in this case, a violation of this principle with a Query changing the state is considered strong and is forbidden

Building your classes following the CQS principle will ensure a higher quality, readability, and maintainability of our classes, with a clearer separation of concern; it’s also easier to stub/mock the classes following this principle (stubs are for queries because they only need to return a fixed data sample, mocks are for commands because they only need to verify how it is called) and in general has a lot of benefits in our code.

There is a strong relation between CQS and CQRS: basically, CQRS raises the CQS pattern to a higher architecture layer, embracing the separation between Commands and Queries at the entire application level - meaning that we want to separate Queries from Commands at an architectural level in the same way we typically keep different modules separated.

The separation can be physical or logical, but it must be clear.

Let’s try to describe a simple sample implementation of CQRS in an application:

  • dedicated controllers/command handlers/data layer for the write side

    // pseudo code example - write side
    
    class Controller {
     public function addElementToList(Element $element): void;
    }
    
    class Service {
     public function executeAddElementToListCommand(AddElementToList $command): void; // used from Controller
    }
    
    class ListRepository {
     public function add(Element $element): void; // used from Service
    }
    
  • separated data layer for write and read - this can be achieved in multiple ways but in general, you will need a way to calculate the read side starting from the right side, some examples to implement that are:

    • Database views

    • Scheduled cron jobs writing in dedicated tables/database

    • Events

    • Event Sourcing projections

  • dedicated controllers/command handlers/data layer for the read side

    // pseudo code example - read side
    
    class Controller {
     public function getList(): Collection<Element>;
    }
    
    class Service {
     public function executeGetListQuery(): Collection<Element>;
    }
    
    class ListRepository {
     public function all(): Collection<Element>;
    }
    

💡 Embracing the low-code and no-code technologies available today, you can automate some of this to reduce the boilerplate code.

For example, what I typically do:

  • when performances are not a priority, I use DB views to build the read side model with the expected fields already in place and then use a tool to expose those views automatically via REST/GraphQL (pgRest/Hasura)

  • when performances matter, the calculation can be done with some async script, even better if they are triggered via Events instead of being scheduled cron jobs

  • for MVPs, I’ve also handled the write side via automated REST API, for example using Supabase and having separated tables for write and read (you can also use access control rules to block writing on the read side and vice-versa)

  • no-code can be useful also for building the UI, both for the write side and in some use cases for read-side data visualization

  • last tip: consider Make or Zapier as tools to implement automated triggers with ease

I typically favor custom API based on feature names over REST, but since write and read are separated I can easily accept automate and standardize the less important side and focus on the most important one with the customization it needs - depending on the use case, I’ve done it on both sides and it works pretty well, reducing the codebase to maintain and simplifying things.

Make sure Event Sourcing is needed

Now, let’s introduce the villain of our story: Event Sourcing.

Event Sourcing is a pattern that mostly impacts how we handle the persistence layer of the state of our application: in a typical application, when something happens that requires changing the state, we simply change it from A to B, and then persist B as a new state. With ES, instead, we create an Event describing the change from A to B and we store the Event itself - then, an async process will execute the Events and therefore transform A into B.

As you can easily understand, this pattern has multiple positive consequences:

  • we can query the Events directly

  • we can rebuild the state up to a given date in the past

  • we can add retroactive Events to fix the state

Event Sourcing in general is a great and powerful technique… so, why did I call it the villain of this story?

First of all, ES is costly: it has a high learning curve to learn how to implement it, and it is complex to learn how to maintain an application based on such a paradigm.

But today we are talking about CQRS, and the big problem I have with ES is the misconception it brings to CQRS: the two paradigms work very well together, therefore are usually presented and described together and most people think that CQRS only makes sense together with ES.

This is simply not true.

On one side, it’s undeniable that CQRS naturally fits with some other architectural patterns, not only Event Sourcing.

  • We can easily move away from a CRUD to a task-based approach.

  • CQRS fits well with event-based programming models in general.

  • CQRS is a good fit already for simple domains, but it’s also great for complex domains that also benefit from Domain-Driven Design.

On the other side, it’s also very true that CQRS deserves its spotlight in Software Development, and it’s a good paradigm for building and maintaining an application with ease.

Anyway, even if CQRS brings a lot of benefits, it adds complexity to our codebase so we need to be cautious about using it: some systems fit well with an implementation based on data that is changed the same way it’s read, and in that case, adding CQRS would just add complexity without any real benefit. CQRS is a great tool to know and have in the toolbox, but even if not as complex as ES, it’s still complex enough to be used with consciousness.

Typically is a good idea to only use it on specific sections of a system instead of the entire system: a specific DDD’s Bounded Context, for example, the one where having read and write separated adds more value than complexity. One of the typical benefits of the pattern, for example, is when your system has a disparity in read and write load, because this pattern easily allows you to scale them independently.

Until next time, happy coding! 🤓👩‍💻👨‍💻


CQRS allows you to separate the load from reads and writes allowing you to scale each independently. If your application sees a big disparity between reads and writes this is very handy.

[Martin Fowler]


Go Deeper 🔎

📚 Books

📩 Newsletter issues

📄 Blog posts

🎙️ Podcasts

🕵️‍♀️ Others

Did you enjoy this post?

Express your appreciations!

Join our Telegram channel and leave a comment!Support Learn Agile Practices

Also, if you liked this post, you will likely enjoy the other free content we offer! Discover it here: