Command Query Separation (CQS) – a quick overview

When I was writing a series of posts about CQRS, I thought it would be a good idea to precede it with a short post about its predecessor called CQS. The idea of the CQS was introduced by Bertrand Meyer and is based on a simple statement:

Asking a question should not change the answer.

Bertrand Meyer

This means that methods should either retrieve data or change the state of the system. Never both.

According to that rule, we distinguish two types of methods:

  • Commands change the state of the system but don’t return anything. If command fails, throws an exception.
  • Queries return a value and don’t change the state of the system.

Let’s see some code.

<?php
namespace App;

use Ramsey\Uuid\Uuid;

class ProductService {
    private $products;

    public function __construct(ProductsRepositoryInterface $productsRepository)
    {
        $this->products = $productsRepository;
    }

    public function create(Uuid $id, string $name, int $amount = 0): Product
    {
        if($this->products->findProductByName($name)) {
            throw new \InvalidArgumentException("A product with name $name already exists");
        }

        $product = new Product($id, $name, $amount);
        $this->products->save($product);

        return $product;
    }
}

As you can see, create() method both modifies the system state and returns a value so it breaks the CQS principle. And this is how it should look like.

<?php
namespace App;

use Ramsey\Uuid\Uuid;

class ProductService {
    private $products;

    public function __construct(ProductsRepositoryInterface $productsRepository)
    {
        $this->products = $productsRepository;
    }

    public function create(Uuid $id, string $name, int $amount = 0): void
    {
        if($this->products->findProductByName($name)) {
            throw new \InvalidArgumentException("A product with name $name already exists");
        }

        $product = new Product($id, $name, $amount);
        $this->products->save($product);
    }

    public function getByName(string $name): ?Product
    {
        return $this->products->findProductByName($name);
    }
}

The main advantage of using this approach is a transparent interface of the class where safe and idempotent methods (queries) are separated from unsafe ones. Commands can be either idempotent or not but they are never safe.

CQS is one of the easiest patterns to understand and implement, yet it can significantly increase the readability and maintainability of code.

Leave a Reply

Your email address will not be published. Required fields are marked *