CQS Interactors

Introduction

The term ‘command query separation’ was coined by Bertrand Meyer in his book “Object Oriented Software Construction“ - a book that is one of the most influential OO books during the early days of OO. (The first edition is the one that had the influence, the second edition is good but you'll need several months in a gym before you can lift it.)

The fundamental idea is that we should divide an object's methods into two sharply separated categories:

  • Queries: Return a result and do not change the observable state of the system (are free of side effects).
  • Commands: Change the state of a system but do not return a value.

©Martin Fowler1

Command Methods:

  • Purpose: Change the state of an object or system.
  • Return Type: Void (no data is returned) or, in some implementations, a status indicating the outcome (failed or succeeded, for instance).

Query Methods:

  • Purpose: Retrieve data from an object or system; they do not change the state.
  • Return Type: Typically, these methods return data.

Dependency Graph

Dependency Graph

Class Diagram

Class Diagram

Benefits of Following CQS:

  • Simplicity: Separating commands and queries simplifies understanding how a particular method affects the system since each method either performs an action or retrieves data, but never both. This enhances the maintainability and readability of code.
  • Reduced Side Effects: By ensuring queries do not change the state of the system, developers can call query methods without worrying about unintended modifications or side effects affecting the application state.
  • Easier Debugging and Testing: Since commands and queries are separated, they can be tested independently. Command methods can be tested for their effectiveness in modifying state, whereas query methods can be tested for their accuracy in returning data. Enhanced Traceability and Control: Understanding system behavior becomes straightforward when system functions are clearly outlined as either commands or queries. It also gives more control while troubleshooting, as changes to the system's state have explicit sources.

Implementing CQS:

  • Interface Design: Interfaces should clearly differentiate between commands and queries. This can be achieved by grouping command methods and query methods separately, possibly in different interfaces or service classes.
  • Method Design: Methods should be designed to strictly adhere to either command tasks (changing state) or query tasks (retrieving state). Developers must avoid mixing responsibilities in a single method.
  • State Management: Ensure that methods altering the system’s state do not directly return the state. Instead, place any necessary queries to inspect state changes in separate query methods.

Application and Impact:

Direct Application: In a simple object-oriented system, implementing CQS can mean designing class methods that either perform actions or return data, but do not do both. Broader Implication - CQRS: When scaled to the architecture level, the CQS principle leads to the CQRS (Command Query Responsibility Segregation) pattern, where command operations (writes) and query operations (reads) are separated even further, often using different models and data storage.

For modern software applications, especially those dealing with complex domains and large datasets, adhering to the CQS principle helps in designing a clear, maintainable, and scalable architecture. By ensuring a distinct separation between how the data is manipulated and how it is retrieved, systems become more robust and less prone to errors introduced by unintended side effects.

References