CQRS pattern: advantages of Command and Queries

Daniele Scillia (Dan The Dev)
Learn Agile Practices
4 min readFeb 20, 2024

--

CQRS pattern is typically discussed only in pair with Event Sourcing — but while not all projects might justify ES, any project can make use of CQRS: let’s discover why!

A “chibi style” happy software developer programming from the beach, made with ideogram.ai

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
- 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.

[ … continue … ]

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

🙏 Thank you for reading this shortened version of this article. You can enjoy the full version on my blog here.

--

--

Daniele Scillia (Dan The Dev)
Learn Agile Practices

Software Engineer @TourRadar - Passionate Dev, XP Advocate, passionate and practitioner of Agile Practices to reach Technical Excellence