CQRS
Separates the read model from the write model to optimize each independently.
Context
Your reads and writes have very distinct shapes and requirements: queries need denormalization and caching; commands must validate complex invariants.
Problem
A single model trying to serve both optimizes for neither.
Solution
Two stacks: Commands (mutate, return only success/failure) and Queries (read, return DTOs adapted to the view).
#Example in C# — with MediatR
// Command (write)
public record PlaceOrderCommand(Guid CustomerId, IReadOnlyList<LineItem> Items) : IRequest<Guid>;
public class PlaceOrderHandler : IRequestHandler<PlaceOrderCommand, Guid>
{
private readonly IOrderRepository _repo;
public PlaceOrderHandler(IOrderRepository r) => _repo = r;
public async Task<Guid> Handle(PlaceOrderCommand cmd, CancellationToken ct)
{
var order = Order.Create(cmd.CustomerId, cmd.Items);
_repo.Add(order);
return order.Id;
}
}
// Query (read)
public record GetOrderSummaryQuery(Guid Id) : IRequest<OrderSummaryDto>;
public class GetOrderSummaryHandler : IRequestHandler<GetOrderSummaryQuery, OrderSummaryDto>
{
private readonly DbConnection _db;
public Task<OrderSummaryDto> Handle(GetOrderSummaryQuery q, CancellationToken ct) =>
_db.QueryFirstAsync<OrderSummaryDto>("SELECT id, total, status FROM order_summary WHERE id=@id", q);
}When NOT to use it
- When the model is simple (CRUD): separation adds ceremony without gain.
- When the team is not comfortable with eventual consistency between read/write models.
Tradeoffs
| Pro | Con |
|---|---|
| Independent optimization | Two models to maintain |
| Reads scale independently | Synchronization (eventual consistency) between models |
#extra #ddd #scaling