Skip to content

CQRS (Command Query Responsibility Segregation) Pattern

Introduction

In traditional CRUD-based systems, the same data model is used for both reads (queries) and writes (commands). While simple, this approach struggles at scale:

  • Queries often require complex joins or aggregations.
  • Writes may need strict validation and transactional guarantees.
  • The same model ends up overcomplicated, trying to satisfy both needs.

The CQRS Pattern (Command Query Responsibility Segregation) addresses this by separating read and write responsibilities.

  • Commands: Modify state (writes).
  • Queries: Retrieve state (reads).

This separation simplifies models, improves performance, and works well with event-driven systems.


Intent

The intent of the CQRS Pattern is to separate command (write) and query (read) responsibilities into distinct models to improve scalability, clarity, and performance.


Structure

Core Components

  1. Command Model

    • Handles state changes.
    • Enforces business rules.
  2. Query Model

    • Handles data retrieval.
    • Optimized for reads (e.g., denormalized views).
  3. Command Handlers

    • Execute commands (e.g., placeOrder).
  4. Query Handlers

    • Execute queries (e.g., getOrderHistory).
mermaid
graph TD
  A[Client] -->|Command| B[Command Handler]
  A -->|Query| C[Query Handler]
  B --> D[(Write Model)]
  C --> E[(Read Model)]

✅ Separation of concerns.
✅ Optimized for both read and write paths.


Participants

  1. Commands

    • Represent user intent to change state.
    • Example: PlaceOrderCommand.
  2. Command Handlers

    • Validate and apply business logic.
    • Persist changes to write model.
  3. Queries

    • Represent data retrieval requests.
    • Example: GetOrderHistoryQuery.
  4. Query Handlers

    • Fetch from read-optimized model.

Collaboration Flow

  1. Client issues command → Command Handler → Write Model.
  2. Client issues query → Query Handler → Read Model.
  3. (Optional) Event sourcing or replication keeps read model updated.

Implementation in Java

Command

java
public class PlaceOrderCommand {
    private final String orderId;
    private final double total;
    public PlaceOrderCommand(String orderId, double total) {
        this.orderId = orderId; this.total = total;
    }
    public String getOrderId() { return orderId; }
    public double getTotal() { return total; }
}

Command Handler

java
@Service
public class PlaceOrderHandler {
    private final OrderRepository repository;
    public PlaceOrderHandler(OrderRepository repository) { this.repository = repository; }

    public void handle(PlaceOrderCommand cmd) {
        if(cmd.getTotal() <= 0) throw new IllegalArgumentException();
        repository.save(new Order(cmd.getOrderId(), cmd.getTotal()));
    }
}

Query

java
public class GetOrderQuery {
    private final String orderId;
    public GetOrderQuery(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
}

Query Handler

java
@Service
public class GetOrderHandler {
    private final JdbcTemplate jdbc;
    public GetOrderHandler(JdbcTemplate jdbc) { this.jdbc = jdbc; }

    public Order handle(GetOrderQuery query) {
        return jdbc.queryForObject("SELECT * FROM orders WHERE id = ?",
            new Object[]{query.getOrderId()},
            (rs, rowNum) -> new Order(rs.getString("id"), rs.getDouble("total")));
    }
}

✅ Write model handles validation.
✅ Read model optimized for queries.


Consequences

Benefits

  1. Optimized Models – Separate read/write models tailored for their purpose.
  2. Scalability – Independent scaling of read vs write workloads.
  3. Clarity – Commands express intent, queries express retrieval.
  4. Event Sourcing Friendly – Commands emit events, queries read projections.
  5. Security – Easier to enforce different authorization on commands vs queries.

Drawbacks

  1. Complexity – More moving parts than CRUD.
  2. Consistency – Eventual consistency between write and read models.
  3. Learning Curve – Developers must understand commands, queries, events.

Real-World Case Studies

1. Banking Systems

  • Commands enforce strict rules for transactions.
  • Queries optimized for statements/history.

2. E-commerce Platforms

  • Order placement (command).
  • Order history lookup (query).

3. Event-Sourced Systems

  • Commands emit events.
  • Read models updated asynchronously for queries.

Extended Java Case Study

Traditional CRUD (Monolithic)

java
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired private OrderRepository repo;

    @PostMapping
    public void placeOrder(@RequestBody Order order) { repo.save(order); }

    @GetMapping("/{id}")
    public Order getOrder(@PathVariable String id) { return repo.findById(id).orElseThrow(); }
}

❌ Same model for reads/writes.
❌ Hard to optimize queries without breaking writes.

CQRS Approach

  • PlaceOrderCommand handled by PlaceOrderHandler.
  • GetOrderQuery handled by GetOrderHandler.
  • Separation allows different optimizations.

✅ Clean separation of responsibilities.


Interview Prep

Q1: What is CQRS?

Answer: A pattern that separates read (query) and write (command) responsibilities into different models and handlers.

Q2: What are pros and cons of CQRS?

Answer: Pros: optimized models, scalability, clarity. Cons: complexity, eventual consistency.

Q3: How does CQRS relate to event sourcing?

Answer: CQRS works well with event sourcing: commands produce events, read models consume them.

Q4: When should you use CQRS?

Answer: For complex domains with different read/write workloads. Avoid in simple CRUD apps.

Q5: Give a real-world example.

Answer: Banking: money transfer = command, account balance lookup = query.


Visualizing CQRS Pattern

mermaid
graph TD
  Client -->|Command| CH[Command Handler]
  Client -->|Query| QH[Query Handler]
  CH --> WM[(Write Model)]
  QH --> RM[(Read Model)]

✅ Clear separation of paths.


Key Takeaways

  • CQRS Pattern separates read and write models.
  • Commands modify state, queries fetch state.
  • Benefits: scalability, optimized models, clarity.
  • Drawbacks: complexity, eventual consistency.
  • Works well with event sourcing and microservices.

Next Lesson

Next, we’ll dive into Event Sourcing Pattern — persisting events instead of state, often combined with CQRS for full power.

Continue to Event Sourcing Pattern →


Connect: LinkedIn

© 2025 Official CTO. All rights reserved.