Command-Query Separation (CQS) with Java Examples
Introduction
This tutorial explains Command-Query Separation (CQS) with Java examples. We will start with what commands and queries are and what the principle asks of them. We will then run an example in which a harmless-looking getter also resets a counter, so that showing overtime on a dashboard means it is never paid - and fix it by separating the query from the command. Next, we will look at the well-known exceptions in the JDK, such aspop() and Iterator.next(), and why they exist. Lastly, we will see how CQS differs from CQRS.
What is Command-Query Separation
Bertrand Meyer introduced Command-Query Separation in his book Object-Oriented Software Construction (1988), and built it into the Eiffel language. It divides methods into two kinds -- Queries return a result and do not change the observable state of the system.
list.size()andemployee.leaveBalance()are queries. - Commands change the state of the system and do not return a value.
list.clear()andemployee.takeLeave(3)are commands.
Why it matters
If queries never change anything, you can call them as often as you like, in any order, and from anywhere - in a log statement, in an assertion, in a debugger's watch window, in a test - without changing what the program does. You can also tell what a line of code does from its shape:x = obj.something() only reads, and obj.doSomething() changes things. A method that does both breaks these assumptions in a way that does not show at the call site.
Example - a getter that changes state
An employee's overtime hours are logged during the month. In the first version,getOvertimeHours() returns the hours and resets the counter, because the original author only expected payroll to call it, once a month. Later, a dashboard was added that shows the current overtime.
Java code showing a query with a side effect and the same class following Command-Query Separation
//CqsExample.java
public class CqsExample {
// ---------- Breaks CQS: a "getter" that also resets ----------
static class TimesheetMixed {
private int overtimeHours;
void logOvertime(int hours) { overtimeHours += hours; }
int getOvertimeHours() { // looks like a query...
int hours = overtimeHours;
overtimeHours = 0; // ...but also resets the counter
return hours;
}
}
// ---------- Follows CQS ----------
static class Timesheet {
private int overtimeHours;
void logOvertime(int hours) { overtimeHours += hours; } // command
int overtimeHours() { return overtimeHours; } // query
void settleOvertime() { overtimeHours = 0; } // command
}
static double overtimePay(int hours) { return hours * 30.0; }
public static void main(String[] args) {
TimesheetMixed mixed = new TimesheetMixed();
mixed.logOvertime(4);
mixed.logOvertime(2);
System.out.println("Mixed - dashboard shows : " + mixed.getOvertimeHours() + " hours");
System.out.println("Mixed - payroll pays : " + overtimePay(mixed.getOvertimeHours()));
Timesheet timesheet = new Timesheet();
timesheet.logOvertime(4);
timesheet.logOvertime(2);
System.out.println("CQS - dashboard shows : " + timesheet.overtimeHours() + " hours");
int hours = timesheet.overtimeHours();
System.out.println("CQS - payroll pays : " + overtimePay(hours));
timesheet.settleOvertime();
System.out.println("CQS - after settling : " + timesheet.overtimeHours() + " hours");
}
}
Mixed - dashboard shows : 6 hours Mixed - payroll pays : 0.0 CQS - dashboard shows : 6 hours CQS - payroll pays : 180.0 CQS - after settling : 0 hours
Explanation of the code
- In
TimesheetMixed, the dashboard's call togetOvertimeHours()reset the counter, so payroll found 0 hours and paid nothing. Neither caller did anything wrong; the method's name promised a query and it behaved as a command. - The same bug appears if anyone adds
log.debug("overtime = " + ts.getOvertimeHours()), or inspects the method in a debugger. Code that only looks at a value has changed it. Timesheetsplits the work into a query,overtimeHours(), and a command,settleOvertime(). The dashboard can call the query as often as it likes; payroll reads the hours and then settles them, as two visible steps.- The command's name says what it does. A reader of the payroll code now sees that overtime is reset, instead of having to know that a getter does it.
Accepted exceptions in the JDK
CQS is a strong default, not an absolute rule. Several JDK methods both change state and return a value, deliberately -JDK methods that are both a command and a query
String top = stack.pop(); // removes the top element and returns it
Employee next = iterator.next(); // advances the iterator and returns an element
Employee old = map.put(id, employee); // replaces a value and returns the previous one
Task task = queue.poll(); // removes the head of the queue and returns it
int n = counter.incrementAndGet(); // AtomicInteger: increments and returns the new value
- Atomicity in concurrent code: with several threads, "read the value, then change it" as two calls is a race condition, because another thread can act in between.
incrementAndGet()andConcurrentHashMap.putIfAbsent()must read and write in one step to be correct. - Established idioms:
pop()andnext()are so well known that no reader is surprised by them. The names announce the state change.
takeNext() or readAndReset(), never get...().
Commands that return something
Two other common cases sit at the edge of CQS -- Fluent setters and builders return
thisso that calls can be chained. They return no information about the object's state, so they keep the spirit of CQS. - Creating something and returning its id, such as
repository.save(employee)returning a generated key. This is widely accepted, because the caller needs the result of the command and there is no separate question it could ask. Alternatively, let the caller generate the id first, and makesave()a pure command.
boolean success flag from a command is usually better replaced by an exception for the failure case, as takeLeave() in the Tell, Don't Ask tutorialClick to read tutorial: Tell, Don't Ask Principle Explained with Java Examples does.
CQS versus CQRS
CQRS (Command Query Responsibility Segregation), named by Greg Young around 2010, takes the same idea to the architecture level. Instead of separating methods, it separates whole models: commands go to a write model that enforces business rules, and queries are answered from one or more read models shaped for the screens and reports that need them, often in different storage. CQRS is a large architectural decision for complex domains; CQS is a habit that costs nothing and fits in every class.CQS and other principles
- Pure functions: a query is a small step towards a pure functionClick to read tutorial: Pure Functions and Side Effects with Java Examples - a method whose only effect is its return value.
- Principle of least astonishment: a getter with a side effect is one of the most surprising things an API can do - see the Principle of Least AstonishmentClick to read tutorial: Principle of Least Astonishment in API Design.
- Design by Contract: Meyer designed CQS so that queries could be used safely inside preconditions and postconditions, which must never change state - see Design by ContractClick to read tutorial: Design by Contract in Java with Examples.
Summary
In this tutorial we first saw Bertrand Meyer's Command-Query Separation: queries return results without changing state, and commands change state without returning results. Next we saw why this makes queries safe to call from anywhere. We then ran an example in which a getter that reset a counter caused overtime to go unpaid as soon as a dashboard displayed it, and fixed it with a separate query and command. We then looked at the JDK's deliberate exceptions, such aspop() and incrementAndGet(), and the reasons for them, and at commands that return this or a generated id. Lastly, we compared CQS with CQRS and related it to other principles.Programming Principles Tutorials
Immutability in Java: Principles and PracticeClick to read tutorial: Immutability in Java: Principles and Practice Pure Functions and Side Effects with Java ExamplesClick to read tutorial: Pure Functions and Side Effects with Java Examples SOLID Principles in Java: Overview with ExamplesClick to read tutorial: SOLID Principles in Java: Overview with Examples Composition over Inheritance in Java with ExamplesClick to read tutorial: Composition over Inheritance in Java with Examples Coupling and Cohesion Explained with Java ExamplesClick to read tutorial: Coupling and Cohesion Explained with Java Examples DRY, KISS and YAGNI Principles with Java ExamplesClick to read tutorial: DRY, KISS and YAGNI Principles with Java Examples Dependency Injection vs Dependency Inversion vs IoCClick to read tutorial: Dependency Injection vs Dependency Inversion vs IoC
Immutability in Java: Principles and PracticeClick to read tutorial: Immutability in Java: Principles and Practice Pure Functions and Side Effects with Java ExamplesClick to read tutorial: Pure Functions and Side Effects with Java Examples SOLID Principles in Java: Overview with ExamplesClick to read tutorial: SOLID Principles in Java: Overview with Examples Composition over Inheritance in Java with ExamplesClick to read tutorial: Composition over Inheritance in Java with Examples Coupling and Cohesion Explained with Java ExamplesClick to read tutorial: Coupling and Cohesion Explained with Java Examples DRY, KISS and YAGNI Principles with Java ExamplesClick to read tutorial: DRY, KISS and YAGNI Principles with Java Examples Dependency Injection vs Dependency Inversion vs IoCClick to read tutorial: Dependency Injection vs Dependency Inversion vs IoC