Commands and Queries are Holistic Abstractions

This post has been updated

Click here


In this post I will outline an updated perspective on Commands and Queries. If you are unfamiliar with these two patterns then please see these posts here and here for a great introduction. For the remainder of this post I will assume you have fully subscribed to the idea of parameter objects and are comfortable with a variation of the following two abstractions.

Command

Actions that change something and return nothing

public interface ICommand { }

public interface ICommandHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

Query

Actions that return something and change nothing

public interface IQuery<TResult> { }

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

I love using these abstractions and they have been an integral part of my developer toolbox for the last few years. I’ve implemented them frivolously and decorated them like Mr Potato Head. But there’s always been this niggling doubt in the back of my mind, something just didn’t feel right. The basis for my confusion was that the two abstractions are presented as a complementary pair but they are not at the same scale. Commands are a container for a transaction, Queries return some data. One is a house, the other a door in a room in a house. Queries can be used by commands (but clearly commands cannot be used by queries).

I have encountered numerous misunderstandings around exactly what a Command and Query are, where they should or should not be applied, and what should be kept separate and when.

  • Can Commands reference other Commands? Maybe.
  • Is a Command an atomic unit of work? Maybe.
  • Can a command reference a query? Maybe.
  • Can a query reference a command? NO.
  • Can a command decorator reference another command? Maybe.
  • Can an event subscriber reference a command. Maybe.

Our favourite abstractions are in danger of becoming

Abstractions that do too much!


I want to remove this ambiguity and in so doing get a better handle on this topic so I’m researching it and here is my first take on my updated view of these abstractions.

Command-Query Separation (CQS) – the birth of Commands and Queries

Bertrand Meyer defines CQS as: every method should either be a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer.

Some rudimentary research has led me to new understanding of Commands and Queries.

Command

Commands initiate state change by executing the appropriate behaviour on the domain

public interface ICommand { }

public interface ICommandHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

Query

Queries report on the current state of the domain

public interface IQuery<TResult> { }

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

But the most important thing I realised is:

Commands and Queries are holistic abstractions. They are concerned with the whole transaction. They are not a piece of something.

This simple definition seems to make Commands and Queries more equal. They are the same size, the same scale. It’s easy enough to see a Command as a holistic abstraction, but less so a Query. Until you realise this:

Queries are a stale view of data; they are a view of the domain at a point in time. Querying the current state of the domain should not be confused with reading data from the database. The reporting database may not yet be in sync with the domain.

*A stale view of data

This kind of neatly describes the C and the Q in CQS, but what about the S? Where is the Separation?

This requires updating one of the golden rules:

A Query should never reference a Command. A Command should never reference Query.

Woah! Slow down there cowboy. That rule sucks. Commands and Queries are cool the way many of us have been using them all this time. Why suggest placing such a limitation on Queries when we’ve been using them in Commands for ever?

What makes Commands and Queries so cool?

Commands and Queries are cool because they offer a simple, consistent and convenient way to apply cross cutting concerns (AOP). (This is clearly not an exhaustive list!)

Can we have AOP and implement the new golden rule?

There must be a way of honouring the original intention of Commands and Queries and retain all the benefits we enjoy today (and maybe a few more). Wouldn’t it be great to have a type of command and query that we can use within our commands and queries? An equivalent set of abstractions at a lower level for both enquiring on and transforming state as part of something bigger. Abstractions that offer all the great benefits we currently enjoy through the separation of behaviour from data: the ability to apply cross cutting concerns by being composed of a common reusable interface and parameter objects.

New low level abstractions

I propose that we need one or more sets of lower level abstractions that work in much the same way as their higher level holistic counterparts but that are understood to only be part of a transaction. (You could almost consider these abstractions to be strategies.)

Transition

Actions that change something and return nothing

public interface ITransitionHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

public interface IDataCommandHandler<TCommand> where TCommand : IDataCommand
{
    void Handle(TCommand command);
}

public interface ICommandStrategyHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

Computation

Actions that return something and change nothing

public interface IComputationHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

public interface IDataQueryHandler<TQuery, TResult> where TQuery : IDataQuery<TResult>
{
    TResult Handle(TQuery query);
}

public interface IQueryStrategyHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

Summary

I am suggesting a renewed definition of Commands and Queries as holistic abstractions and equivalent sets of lower level abstractions that enable applying cross cutting concerns inside our architectures.

Enjoy!

P.S. An ITransitionHandler should never reference an IComputationHandler! ;-)

Leave a Reply