Any time we apply an abstraction to a piece of software, there’s always a tradeoff. This usually takes the form of being seemingly harder to work with, adding a barrier to engineers that just want to write code. But if physics is to be trusted then all things will tend towards chaos, just as the unchecked codebase tends towards spaghetti. Abstractions, and the design patterns they implement, are guardrails against this chaos. They give us broad lines to colour between and allow us to focus on the what, instead of the how.

CQRS, or Command Query Response Segregation is one such pattern that helps increase cohesion and reduce cognitive load in understanding what a domain is capable of. Let’s see how.

The example

It’s Easter as I write this, so let’s take a thematic example. Let’s say you’re organising an egg scavenger hunt and you’d like people to register before hand. Once done, they’ll be given details about the upcoming event. You might end up with something like this:

import express from 'express';

const app = express();

interface Hunt {
  when: Date;
  where: string;
}

interface User {
  email: string;
}

interface HuntRepository {
  getLatest: async () => Hunt;
}

interface UserRepository {
  get: async (email: string) => User;
  save: async(user: User) => void;
}

const getHuntRepository: HuntRepository = () => {
  // todo, write this...
}

const getUserRepository: UserRepository = () => {
  // todo, write this...
}

app.post("/sign-up/", async (req, res) => {
  const email = req.body.email;

  const user: User = { email };

  const userRepo = getUserRepository();
  try {
    await userRepo.save(user)
  } catch UserExistsError {
    res
      .status(400)
      .json({
        message: "User with that email already exists"
      });
    return;
  }

  const huntRepo = getHuntRepository();
  const hunt = await huntRepo.getLatest();

  res.status(201).json(hunt)
});

Lovely, the user only needs sign up with their email, and if they ever need to login we can do so with a “magic-link” to their inbox. So in this request we store a user, find the latest hunt, and return that to the client so they can show the information they need to. Cool.

So what’s wrong with this? Well, that one handler is doing a lot. Maybe more than it should. What happens if we just want to sign a user up? Or just look at the latest hunt? Do we have to replicate that? Sounds like a DRY violation already… there must be a better way.

Commands

The name “command” is shared by a few different places in software engineering, but here it means “an operation that can perform a mutation on a system”. That is to say that this is an unsafe operation, and while it doesn’t have to cause a change, it is allowed to create some kind of side effect on the system.

Like, for example, creating a user. What’s interesting however, is the description above applies to the vast majority of the code that we tend to write. What makes some of them commands, and others just functions that don’t return anything?

Formally recognising commands in your domain describes what it is your domain is capable of. For example, reading a file that has the following:

type BookUnicornCommand = async (name: string, when: date, email: string) => void;

We should have a reasonable idea what we are able to do, but also what this domain is for.

Query

Well, if commands can perform mutations, then queries are simply anything that doesn’t perform a mutation but instead return something. Again, formally describing the queries your domain supports helps us understand what the domain is.

type BookingsQuery = async (name: string) => Booking[]
type UnicornsQuery = async () => string[]

Combined with the commands above, we might surmise that this domain appears to be some kind of unicorn booking service… I can view the unicorns that exist, and book them. For rides? Portraits? Who knows. The point is that with no other information about the system, we can understand its capabilities and from that, its intent.

This is a bit of a digression from CQRS, as this is more a summary of one of the benefits, but you get the idea.

Formally describing the commands and queries your domain supports describes the domain itself.

Back to the example

So if we were to extract and split the functionality we’re supporting here, what might that look like?

const createUserCommand = async (user: User) => {
  const userRepo = getUserRepository();
  await userRepo.save(user);
}

const latestHuntQuery = async () => {
  const huntRepo = getHuntRepository();
  const hunt = await huntRepo.getLatest();
  return hunt;
}

app.post("/sign-up/", async (req, res) => {
  const email = req.body.email;

  const user: User = { email };

  try {
    await createUserCommand(user)
  } catch UserExistsError {
    res
      .status(400)
      .json({
        message: "User with that email already exists"
      });
    return;
  }

  const hunt = await latestHuntQuery();

  res.status(201).json(hunt)
});

Okay I guess that’s a little better. We’ve grouped similar operations together and they now have much higher cohesion. It’s not really saved much code, but it has made the POST endpoint much easier to follow.

But so far we’ve only spoken about the C and Q of CQRS. What about Response Segregation? The segregation here refers to the fact that while you can combine a Command and Query in a single operation, you shouldn’t - and in some cases, you physically cannot.

So taking this one step further, we then have:

app.post("/sign-up/", async (req, res) => {
  const email = req.body.email;

  const user: User = { email };

  try {
    await createUserCommand(user)
  } catch UserExistsError {
    res
      .status(400)
      .json({
        message: "User with that email already exists"
      });
    return;
  }

  res.status(201);
});

app.get("/latest-hunt/", async (req, res) => {
  const email = req.body.email;
  const hunt = await latestHuntQuery();
  res.status(200).json(hunt)
});

We now have two separate endpoints; the POST creates the user and the GET is mutation free. Neat! We didn’t have to do this though; we saw earlier it was fine, so why bother?

The problem with the previous solution was that we coupled the interface we expose to our clients to the implementation behind them. If we wanted to extend our service to support other holidays (beyond Easter), we’d need to make a breaking change to the API which is never fun.

Secondly, there are some advanced patterns that involve fully separating the write from the read, such that the same domain may not even be aware of the other despite having a definition of the entities involved. i.e. it is quite common for one system to store normalised data, with writes being performed there on this data, and another system denormalising it to better respond to read requests quickly. (Another example may be an EventStore, which we may cover in a later what-is.)

So there we are, CQRS is a pattern used to split your reads and your writes, allowing you to decouple their behaviour entirely, while simultaneously helping you describe the capabilities of your domain.

Updated: