C# Architecture Mastery — CQRS in ASP.NET Core (When It Helps, When It Hurts) (Part 9)
Source: Dev.to
Introduction
CQRS is one of the most misunderstood patterns in modern .NET.
Some teams adopt it too early, while others avoid it entirely out of fear. Both mistakes stem from treating CQRS as a framework choice instead of an architectural strategy.
In this Part 9 we’ll explain what CQRS really is, when it brings clarity, and when it actively hurts ASP.NET Core systems.
What CQRS Stands For
Command Query Responsibility Segregation
- Commands change state
- Queries read state
These responsibilities are separated.
What CQRS Does Not Require
- Event sourcing
- Microservices
- Message brokers
- Separate databases
These are optional—not core to CQRS.
Traditional CRUD Services
CRUD services typically mix:
- Reads
- Writes
- Validation
- Business rules
- Mapping logic
into a single abstraction.
// ❌ CRUD service
class OrderService
{
Order Get(int id) { }
void Create(Order order) { }
void Update(Order order) { }
}
As systems grow, this becomes:
- Hard to reason about
- Hard to optimize
- Hard to scale independently
CQRS in Clean Architecture
- Commands live in the Application layer
- Queries live in the Application layer
- Infrastructure implements persistence
- ASP.NET Core adapts HTTP to commands/queries
CQRS fits naturally when boundaries already exist.
Commands
Commands represent intent to change state.
public record CreateOrderCommand(decimal Total);
class CreateOrderHandler
{
public Task Handle(CreateOrderCommand command) { }
}
Characteristics
- Return minimal data (or nothing)
- Contain validation
- Enforce business rules
Queries
Queries represent questions.
public record GetOrderQuery(int Id);
class GetOrderHandler
{
public Task Handle(GetOrderQuery query) { }
}
Characteristics
- Never change state
- Can use optimized read models
- Often bypass domain objects (intentional)
When CQRS Is Beneficial
- Read and write models differ significantly
- Query performance is critical
- Business rules are complex
- Teams want clear intent separation
- Vertical Slice Architecture (VSA) is in use
CQRS shines in medium‑to‑large systems.
When CQRS Hurts
- The system is a simple CRUD app
- Teams lack architectural discipline
- Everything becomes a “handler” → boilerplate overwhelms value
Example of proliferating handlers:
CreateUserCommandHandler
UpdateUserCommandHandler
DeleteUserCommandHandler
GetUserByIdQueryHandler
GetUsersQueryHandler
This can become noise without benefit.
CQRS vs. MediatR
Many teams equate CQRS = MediatR, which is incorrect.
- MediatR is a messaging library and convenience tool.
- CQRS is a design decision and a separation of responsibilities.
You can implement CQRS without MediatR.
CQRS Paired with Vertical Slice Architecture
Each slice contains:
- Command or query
- Handler
- Validator
- Endpoint
Features/
└─ Orders/
├─ Create/
└─ GetById/
This reduces coupling and improves clarity.
Adoption Checklist
Before adopting CQRS, ask:
- Are reads and writes evolving differently?
- Is performance a real problem?
- Is domain complexity growing?
- Will this reduce cognitive load?
- Is the team ready for the required discipline?
If the answer is “no” to any of these, postpone CQRS.
Summary
- CQRS is not a silver bullet.
- Used correctly: clarifies intent, simplifies reasoning, scales gracefully.
- Used incorrectly: adds ceremony, slows teams down, obscures simple logic.
Senior engineers know when not to use it.