CQRS

Mental model

CQRS = Command Query Responsibility Segregation.

Commands change state. Queries read state. They don’t have to use the same model, database, or scaling strategy.

Without CQRS

flowchart LR
    API --> Service
    Service --> DB[("One Model / DB")]

The same model handles:

Create Order
Update Order
Get Order
Search Orders
Order History

This is often perfectly fine.

With CQRS

flowchart LR
    Client --> API["API"]

    API --> Command["Command Side"]
    API --> Query["Query Side"]

    Command --> WriteDB[("Write DB")]
    WriteDB --> Events["Domain Events"]
    Events --> ReadDB[("Read Model")]

    Query --> ReadDB

The write and read sides have different responsibilities.

Command

A command asks the system to perform an action.

CreateOrder
CancelOrder
ReserveInventory
UpdateProduct

Small Java record:

public record CreateOrderCommand(
    UUID customerId,
    List<OrderItem> items
) {}

Small .NET record:

public record CreateOrderCommand(
    Guid CustomerId,
    IReadOnlyList<OrderItem> Items);

Query

A query asks for information without changing state.

GetOrder
SearchProducts
GetCustomerOrders
GetInventoryAvailability

Java:

public record GetOrderQuery(UUID orderId) {}

.NET:

public record GetOrderQuery(Guid OrderId);

Why Separate Them?

Suppose we have:

Orders written 10,000/sec
Orders read     100,000/sec

The read workload is much larger.

With CQRS we can scale independently:

                 ┌── Write Service × 3
Client → API ────┤
                 └── Read Service × 20

The read model can also be shaped specifically for queries.

Read Model

Instead of reconstructing an order from many normalized tables:

orders
order_items
products
customers
shipments
payments

we can maintain a query-friendly projection:

{
  "orderId": "123",
  "customerName": "Alice",
  "status": "SHIPPED",
  "total": 129.99,
  "items": [
    {"name": "Keyboard", "quantity": 1}
  ],
  "trackingNumber": "ABC123"
}

The query doesn’t need to perform a complicated multi-table read every time.

CQRS Does NOT Require Microservices

CQRS can exist inside one application:

One application

├── Command handlers

├── Query handlers

└── One database

It can also evolve into separate services/databases.

Interview mistake

CQRS does not automatically mean two microservices, two databases, Kafka, or event sourcing.

Those are possible architectural choices, not the definition of CQRS.

CQRS Does NOT Require Event Sourcing

You can use:

CQRS + PostgreSQL

without event sourcing.

Or:

CQRS + Kafka

without event sourcing.

Or:

CQRS + Event Sourcing

if the domain actually benefits from it.

Keep these concepts separate.

Simple Spring Structure

class CreateOrderHandler {
 
    OrderId handle(CreateOrderCommand command) {
        // validate
        // create aggregate
        // persist
        // publish event
        return orderId;
    }
}

Query side:

class GetOrderHandler {
 
    OrderView handle(GetOrderQuery query) {
        return orderReadRepository.find(query.orderId());
    }
}

Simple .NET Structure

public async Task<Guid> Handle(
    CreateOrderCommand command)
{
    // validate
    // persist
    // publish event
    return order.Id;
}

Query:

public Task<OrderView?> Handle(
    GetOrderQuery query)
{
    return readRepository.GetAsync(query.OrderId);
}

When NOT to Use CQRS

Don’t use it merely because:

“CQRS is a microservices best practice.”

It adds:

  • additional models
  • synchronization complexity
  • eventual consistency concerns
  • more code
  • more operational components

For a simple CRUD application:

Controller → Service → Repository → DB

may be better.

Remember

CQRS is about separating the responsibility of changing state from reading state.

It is useful when the read/write workloads, models, scaling requirements, or business complexity are sufficiently different.

Interview Questions

Does CQRS improve performance automatically?

No. It gives you architectural freedom to optimize reads and writes independently. The benefit comes from how you exploit that separation.

Does CQRS mean eventual consistency?

Not necessarily. If command and query models share one database, they can be strongly consistent. A separate asynchronously updated read model commonly introduces eventual consistency.

Why have a separate read model?

To optimize the representation and access pattern for queries without forcing the write model to serve every read use case.

Next

CQRS with Event Driven Architecture