CQS and its impact on design

Let’s start from the abbreviation. CQS stands for Command-Query Separation. The term was invented by Bertrand Meyer during his work on Eiffel programming language in the middle of 1980x. In a nutshell, it states:

Every method should either be a command that performs an action, or a query that returns data to the caller, but not both.

Sounds good. But what does it mean in practice? Let’s look at the trivial example.

Suppose we have some service which could get user from some storage and send message to that user based on the email address:

public class User
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

public interface IStorage
{
    User GetUser(string email);
}

public interface IMessageService
{
    void Send(string email, string message);
}

public class InMemoryStorage : IStorage
{
    public User GetUser(string email) =>
        new User
        {
            Id = 1,
            Email = "bruce@eternity.net",
            FirstName = "Bruce",
            LastName = "Lee",
            BirthDate = new DateTime(1940, 11, 27)
        };
}

public class EternityMessageService : IMessageService
{
    public void Send(string email, string message)
    {
        Console.WriteLine($"Sending '{message}' to {email}...");
    }
}

public class Service
{
    private readonly IStorage storage;
    private readonly IMessageService messageService;

    public Service(IStorage storage, IMessageService messageService)
    {
        this.storage = storage;
        this.messageService = messageService;
    }

    public User GetUser(string email) => storage.GetUser(email);
    public void Send(string message, string email) => messageService.Send(email, message);
}

Service exposes two public methods:

User GetUser(string email)
void Send(string message, string email)

From public API of the service you should be able to distinguish where is query and where is command methods.

For User GetUser(string email) method we expect to pass e-mail as a parameter and get back a user object as a result. It perfectly fits in the query method definition. We do not expect to see signature like void GetUser(string email). It will be very confusing: why it states GetUser and doesn’t return any value? In the same manner we expect to see the command method void Send(string message, string email) with no return value and with two input parameters message and email. This clearly states that in order to operate, method requires input and produces side effect in the form of sending a message.

Good design promotes having method signatures with clear intention.

Consumer can execute code with following snippet:

void Main()
{
    var service = new Service(new InMemoryStorage(), new EternityMessageService());
    var user = service.GetUser("bruce@eternity.net");
    // Some logic with that user...
    service.Send("A wise man can learn more from a foolish question than a fool can learn from a wise answer", user.Email)
}

Now, let’s assume that we have a new requirement:

When today is the Birthday of the user we would like to send an e-mail with congratulations.

This is a typical scenario for a lot of web sites like forums. Also, let’s assume that developer who is going to implement this change not familiar with CQS. So he decides to extend GetUser method with a new logic:

public User GetUser(string email)
{
    var user = storage.GetUser(email); 
    if (user.BirthDate.Day == serverDate.Day && user.BirthDate.Month == serverDate.Month)
    {
        Send($"Congrats with your Birthday, {user.FirstName}! We wish you could stay with us for longer", user.Email);
    }
    return user;
}

Now, running same Main() method results in following output:

Sending 'Congrats with your Birthday, Bruce! We wish you could stay with us for longer' to bruce@eternity.net...
Sending 'A wise man can learn more from a foolish question than a fool can learn from a wise answer' to bruce@eternity.net...

And bingo! We have a violation of command and query separation. The consumer of a service does not expect this method to send a message in addition to its main purpose of retrieving a user. And it is not obvious looking at the signature of this method. By reading method definitions you should be able quickly identify to which category it belongs.

Now let’s see on one of the main properties of query methods:

Any call to the same query method with the same parameters should always give the same result. In other words, query method should be idempotent.

Correct way to implement Birthday requirement would be to move this logic in Main() or to wrap it in any other command method which could accept user and message as input parameters, keeping GetUser method simple user query:

void Main()
{
    var service = new Service(new InMemoryStorage(), new EternityMessageService());
    var user = service.GetUser("bruce@eternity.net");
    if (user.BirthDate.Day == serverDate.Day && user.BirthDate.Month == serverDate.Month)
    {
        service.Send($"Congrats with your Birthday, {user.FirstName}! We wish you could stay with us for longer", user.Email);
    }
    // Other logic here...
    service.Send("A wise man can learn more from a foolish question than a fool can learn from a wise answer", user.Email)
}

It’s worth to mention opposite scenario:

Calling query method from command method is quite legit.

This is true because query does not have a side effect and does not change the state of the system.

It’s also good to mention, that there cases when you need a value back from a command. For example Send method need to return id for further tracking. If you change signature to something like int Send(string message, string email). It will no longer be pure command method, but mixture of command and query which violates CQS principle. In that case possible solution would be to create another query method for the purpose of getting tracking id: int GetTrackingId(string email). So we could always adhere to CQS by decomposing query and command methods. Sometimes the price of following pure separation is performance or changes in other layers. Like in example with Send we most probably need to preserve tracking id somewhere in order to fetch it for int GetTrackingId(string email) call. But it’s always a trade-off like with everything in software development.

NB: CQS has relation with another concept: Design by Contract (DbC) which also invented by Bertrand back in the 1986 when he was working on Eiffel design. CQS is beneficial to DbC because any value-returning method (any query) can be called by any assertion without fear of modifying program state.

Conclusion

The code we write should be readable and maintainable. That is the major quality of a good code. Applying CQS in practice lead to clear public APIs and some sort of trust in what it does. Separation in asking for program state and request to change a program state is beneficial for creating good designs. Perhaps that is the reason why more modern architectural patterns like CQRS for building distributed systems based on well-proof ideas from mid 80-x.

One thought on “CQS and its impact on design

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s