Command Query Separation (CQS) is not about separating how you read data from how you write it; it’s about separating the intent of the operation.

Let’s see this in action. Imagine a simple e-commerce system. We have two fundamental operations:

  1. Command: Placing an order. This changes the state of the system.

    # Conceptual Python
    def place_order(user_id: str, items: list[str], shipping_address: str):
        # ... complex logic to validate inventory, calculate totals, create order record ...
        print(f"Order placed for user {user_id} with items {items}.")
        return order_id
    

    This function does something that has a side effect. It doesn’t return the order data itself, but perhaps an ID or a success/failure status.

  2. Query: Retrieving an order’s details. This reads state without changing it.

    # Conceptual Python
    def get_order_details(order_id: str):
        # ... logic to fetch order data from a database ...
        order_data = {"order_id": order_id, "user_id": "user123", "items": ["book", "pen"], "status": "processing"}
        print(f"Retrieving details for order {order_id}.")
        return order_data
    

    This function asks for information. Its sole purpose is to return data, and it must not modify anything in the system.

The core principle is that a method should be either a command (performs an action, returns void or a status) or a query (returns data, performs no side effects). You should never have a method that does both.

Think about the implications. When you call a method, you should immediately know if it’s going to change anything. This predictability is gold.

  • Simplicity and Readability: Code becomes easier to understand. If a method name starts with get_ or fetch_, you know you’re just reading. If it’s create_, update_, delete_, place_, send_, you know you’re writing.
  • Testability: Commands and queries can be tested in isolation. You can test that a command correctly changes state without worrying about what data it returns. You can test that a query returns the correct data without worrying about whether it accidentally modified something.
  • Concurrency and Caching: This is where CQS really shines. Because queries are guaranteed not to have side effects, they are inherently safe to run concurrently. You can have multiple threads or processes calling get_order_details simultaneously without any risk of data corruption. This also makes caching much simpler: if you know a method never changes state, you can cache its results indefinitely (or until the underlying data is explicitly invalidated by a command).

The system handles these operations distinctly. When a command is issued, the system executes a series of steps that mutate its state. This might involve updating a database, publishing an event, or sending a message. When a query is issued, the system accesses its data store, retrieves the requested information, and returns it. The two paths are fundamentally different in their execution and their impact on the system.

The most surprising aspect is how CQS directly enables Command Query Responsibility Segregation (CQRS), even though they are distinct. CQS is the principle that a method should be a command or a query. CQRS is an architectural pattern that leverages this principle by physically separating the data models and infrastructure for commands and queries, often using different databases or data access strategies. You can have CQS without CQRS (e.g., a single database with distinct command and query methods), but you can’t effectively implement CQRS without adhering to CQS.

The next step in understanding this is to see how this separation can lead to vastly different data models for reads and writes.

Want structured learning?

Take the full Cqrs course →