Introduction
CQRS separates reads from writes — ShopNest Order Management uses MediatR so controllers send one command/query object and handlers contain the logic.
After this article you will
- Implement commands and queries with IRequest
- Build handlers and pipeline behaviors
- Publish domain events as INotification
- Combine CQRS with Clean Architecture layers
- Understand read vs write model trade-offs
Prerequisites
- Article 44 — Clean Architecture
- ShopNest API, EF Core, and DI from prior modules
Concept deep-dive
public record CreateOrderCommand(string CustomerId, List<OrderLineDto> Lines)
: IRequest<Guid>;
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
public async Task<Guid> Handle(CreateOrderCommand req, CancellationToken ct)
{
var order = Order.Create(req.CustomerId, req.Lines);
await _repo.AddAsync(order, ct);
await _publisher.Publish(new OrderCreatedEvent(order.Id), ct);
return order.Id;
}
}
public record GetOrderQuery(Guid OrderId) : IRequest<OrderDetailDto?>;
// Pipeline behavior — validation for all commands
public class ValidationBehavior<TReq, TRes> : IPipelineBehavior<TReq, TRes>
where TReq : IRequest<TRes>
{
public async Task<TRes> Handle(TReq req, RequestHandlerDelegate<TRes> next, CancellationToken ct)
{
await _validator.ValidateAndThrowAsync(req, ct);
return await next();
}
}
Same DB vs separate: Start with one database; split read models when read scale demands it. Event sourcing is optional advanced topic.
Hands-on — ShopNest Order Management System
- Install MediatR; register from Application assembly.
- CreateOrderCommand + GetOrderQuery handlers.
- LoggingBehavior and ValidationBehavior pipeline.
- OrdersController: await _mediator.Send(command).
Common errors & best practices
- Fat handlers with 200 lines — extract domain services.
- CQRS for every CRUD — overkill on simple modules.
- Forgetting async all the way through handlers.
Interview questions
Q: CQRS benefit?
A: Independent scaling and models for reads vs writes; clearer intent per operation.
Q: MediatR purpose?
A: In-process mediator decoupling controllers from handlers + pipeline behaviors.
Q: IRequest vs INotification?
A: IRequest returns value (command/query); INotification is fire-and-forget event.
Summary
- Commands mutate; queries read — separate handlers
- MediatR pipeline cross-cuts validation and logging
- Domain events decouple side effects (email, inventory)
- Fits Clean Architecture Application layer perfectly
Previous: Clean Architecture
Next: Repository Pattern — Deep Dive
FAQ
MediatR performance?
Tiny in-process dispatch overhead — negligible vs DB I/O.
Event sourcing required?
No — CQRS does not imply event sourcing.