Command-Query Separation

Michael Feathers in “Working Effectively with Legacy Code” advises to separate methods and functions into two categories: commands and queries. This is a very simple idea, but it helps to make the code more readable and predictable, and the ideas of methods and functions cleaner and more understandable.

For convenience, I will call both class methods and functions simply as functions, so that I don’t have to write “function or method” every time.

What are Commands and Queries?

Query is a function that returns the result and has no side effects. For example:

const isEqual = (a, b) => a === b;

class DeepThought {
	answerTheUltimateQuestionOfLifeTheUniverseAndEverything() {
		return 42;
	}
}

Command is a function that changes the system state and returns nothing. An example of a command that increments the counter value:

const state = { counter: 0 };
const increaseCounter = () => {
	state.counter++;
};

// Or using a class:
class Counter {
	constructor() {
		this.state = 0;
	}

	increase() {
		this.state++;
	}
}

Feathers advises not to mix the logic of commands and queries. That is, a function can be either a command or a query, but cannot be both at the same time. There are several advantages to this approach.

Clearer Intent

Separation of queries and commands avoids situations where it’s not clear without executing the code what this code does and what the result to expect. For example, in this line it is not clear what will end up in updated:

// New rating? User ID? Something else?..
const updated = userAccount.updateRating(10);

And it’s not obvious from the name of the updateRating function that it can return anything at all. If we want the name to be a talking one, we have to write something like this:

// Ahem... Maybe the rating is returned? Anyway, it's better to check the sources...
const updatedRating = userAccount.updateRatingAndGetUpdatedValue(10);

Here we see two actions, so it is more correct to make two functions. In our case, one command and one query:

// Rating updating command:
userAccount.updateRating(10);

// Query for getting the latest rating:
const updatedRating = userAccount.getRating();

Automatic SRP

When we stop mixing up state changes and data requests, we automatically get closer to creating functions and classes so that they have a single reason to change.

Let’s say, here’s a unnecessary complex function updateSubscriptions. It takes as input an array of tags to which the user subscribes, creates a subscription object, checks whether it is available for the current user, whether the user is active, adds the subscription to the array, updates the rating:

class UserAccount {
	constructor(userAccountInfo) {
		const { rating = 0, active = false, subscriptions = [] } = userAccountInfo;

		this.subscriptions = subscriptions;
		this.active = active;
		this.rating = rating;
	}

	_createLabel(tag) {
		return `Label for ${tag}`;
	}

	_defineType(tag) {
		return tag.includes('secret_code') ? TYPES.PREMIUM : TYPES.REGULAR;
	}

	isSubscriptionValid(subscription) {
		// Some complex validation logic...
		return true;
	}

	updateSubscriptions(tags) {
		tags.forEach((tag) => {
			const label = this._createLabel(tag);
			const type = this._defineType(tag);
			const subscription = { label, type };

			if (this.isSubscriptionValid(subscription) && this.active) {
				this.subscriptions.push(subscription);

				if (type === TYPES.PREMIUM) this.rating += 5;
				else this.rating++;
			}
		});
	}
}

Let’s simplify it by breaking functions into commands and queries.

class UserAccount {
	// ...Constructor and older functions.

	// Query: no side effects, returns subscription object.
	_createSubscription(tag) {
		const label = this._createLabel(tag);
		const type = this._defineType(tag);
		return { label, type };
	}

	// Also query: returns state.
	isUserActive() {
		return this.active;
	}

	// Command: returns nothing, changes state of the `subscriptions` field.
	appendSubscription(subscription) {
		this.subscriptions.push(subscription);
	}

	// Also command: changes the rating value.
	increaseRatingBy(delta) {
		this.rating += delta;
	}

	// Query again: returns delta without changing the state.
	_calcRatingDeltaForSubscription(subscription) {
		if (subscription.type === TYPES.PREMIUM) return 5;
		return 1;
	}

	updateSubscriptions(tags) {
		// With command-query separation,
		// we found that we can use early return!
		if (!this.isUserActive()) return;

		tags.forEach((tag) => {
			// We are not bound now to the subscription data structure;
			// Also it became clearer that it would be a good idea
			// to put the work with subscriptions into a separate class.
			const subscription = this._createSubscription(tag);
			if (!this.isSubscriptionValid(subscription)) return;

			// Adding a subscription is not tied to the data structure;
			// we can replace the array with a stack, and it will be enough to tweak
			// only `appendSubscription` to keep the functionality working.
			this.appendSubscription(subscription);

			// No magic numbers now;
			// Rating change calculation is in one place.
			const ratingDelta = this._calcRatingDeltaForSubscription(subscription);

			// Rating update is called accordingly;
			// It became clear what this function does
			this.increaseRatingBy(ratingDelta);
		});
	}
}

In fact, updateSubscriptions has become a facade for other functions. It gives a common understandable name for the set of actions we want to call in order.

The functions themselves are simpler, have fewer actions, and the actions are grouped by “domain parts”. Because of this, it has become apparent that some actions are better moved to a different class because they are a different domain part.

Also, we no longer need to execute code to understand what’s going on in it. Hence, there is less opportunity for errors to occur.

Higher Testability

Simple entities are easier to test. We don’t need to setup the complex environment and create 100500 fake objects to run a test. We only need the fake that we need to test a particular function or class.

This is, of course, more of a credit to SRP, but still.

Higher Scalability

We can develop commands independently of queries and vice versa. The benefit is that all sorts of twistedly complex data fetches stop cluttering up the class or function code. We can pull all queries into a separate place and store them there.

It’s the same with commands: nothing prevents you from making commands abstract, putting them in a separate file and using them through the whole application.

Sometimes It’s Overkill Though

There are situations where such a division is unnecessary, or when it’s just more convenient to mix the logic. For example, here is a service that registers a new user:

const userId = remoteUserApi.signup({ login, password });

We may be fine with it returning an ID after a successful registration, and we won’t need to split it.

Or we may have no control over this part of the code. Then if we want to use splitting into commands and queries, we will have to write a dispatcher to handle this. And this is an additional abstraction, which can complicate the project.

Resources

Some useful books:

And other stuff: