1. What is CQRS?
CQRS stands for Command Query Responsibility Segregation.
It means separating the read (queries) from the write (commands) side of your application.
Command → modifies data (Create, Update, Delete)
Query → retrieves data (Read-only)
Instead of a single model handling both reads and writes, CQRS uses two separate models .
🔹 2. Why use CQRS?
Scalability → Read and write workloads can scale independently
Performance → Queries can be optimized (denormalized DB, caching) without affecting writes
Flexibility → Supports event sourcing and async messaging
Separation of Concerns → Clearer design between commands and queries
🔹 3. Typical Architecture
Client → API → Command Side (Write model) → Database (writes)
↘︎ API → Query Side (Read model) → Database (reads)
The write side updates the domain model and stores events/state.
The read side provides fast, possibly denormalized access to data.
In distributed systems, they may have separate databases.
Command (Write)
public record CreateOrderCommand(string ProductName, int Quantity) : IRequest<int>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int>
{
private readonly AppDbContext _db;
public CreateOrderHandler(AppDbContext db) => _db = db;
public async Task<int> Handle(CreateOrderCommand request, CancellationToken ct)
{
var order = new Order { ProductName = request.ProductName, Quantity = request.Quantity };
_db.Orders.Add(order);
await _db.SaveChangesAsync(ct);
return order.Id;
}
}
Query (Read)
public record GetOrdersQuery() : IRequest<List<OrderDto>>;
public class GetOrdersHandler : IRequestHandler<GetOrdersQuery, List<OrderDto>>
{
private readonly AppDbContext _db;
public GetOrdersHandler(AppDbContext db) => _db = db;
public async Task<List<OrderDto>> Handle(GetOrdersQuery request, CancellationToken ct)
{
return await _db.Orders
.Select(o => new OrderDto(o.Id, o.ProductName, o.Quantity))
.ToListAsync(ct);
}
}
🔹 5. Advantages
Scalability (reads/writes scale independently)
Optimized models (read side can be tailored for UI needs)
Fits well with Event Sourcing & microservices
Better separation of concerns
🔹 6. Drawbacks
Complexity → More moving parts (commands, queries, handlers)
Eventual Consistency → Read model may lag behind write model in distributed systems
Infrastructure Overhead → Might require message bus, projections, or multiple DBs
Not needed for simple CRUD apps
🔹 7. When to Use
✅ Best for:
Large enterprise systems
Read-heavy applications (e.g., dashboards, e-commerce product catalogs)
Event-driven microservices
Systems needing audit trails & history
❌ Avoid for:
⚖️ CQRS Pros vs Cons
Aspect | Pros ✅ | Cons ⚠️ |
---|
Scalability | Reads and writes can scale independently | More moving parts → harder to manage infra |
Performance | Queries can use denormalized models, caching, or NoSQL for speed | Possible eventual consistency (read may lag behind write) |
Separation of Concerns | Clear distinction: Commands = state changes, Queries = reads only | Extra boilerplate (commands, queries, DTOs, handlers) |
Flexibility | Can use different databases for read/write sides | Requires integration between multiple stores |
Event Sourcing | Natural fit, allows audit logs, replay, and history tracking | Adds complexity in storage & replay logic |
Maintainability | Cleaner domain model, easier to evolve business rules | Steeper learning curve for new teams |
User Experience | Read side tailored for UI → faster responses | Debugging across async events + projections is harder |
Best For | Large-scale, complex, event-driven apps (e.g., e-commerce, banking) | Not ideal for simple CRUD apps or MVPs |