Requests e Commands com MediatR
ResumoImplementamos CQRS com MediatR: uma
Querypara leitura e umCommandpara escrita, cada um com seu handler. O controller apenas envia a mensagem viaISender.
Notaa partir da v13.0.0 (2 de julho de 2025, Lucky Penny Software), o MediatR adota modelo comercial para uso empresarial. A v12.x (última livre, MIT) permanece gratuita. Avalie a licença antes de adotar a v13+ em produção; alternativas open-source (ex.: Wolverine, ou um mediator próprio) existem. Os conceitos de CQRS/Mediator desta parte valem para qualquer implementação.
1. Objetivos de Aprendizagem#
- Instalar e registrar o MediatR.
- Criar queries e commands com seus handlers.
- Fazer o controller enviar mensagens em vez de chamar serviços.
2. Pré-requisitos#
- Conceito de CQRS/Mediator (tópico 44.2).
3. Conceito#
No MediatR, cada caso de uso é uma mensagem (IRequest<TResponse>) tratada por um IRequestHandler. O controller injeta ISender e faz Send(mensagem). O porquê: o controller não conhece a implementação — só a intenção. Isso mantém controllers finos e a lógica em handlers testáveis.
4. Mão na Massa#
4.1. Setup#
dotnet new classlib -n Catalog.Application -f net9.0
dotnet add Catalog.Application package MediatR
dotnet add Catalog.Api reference Catalog.Application
Registro:
builder.Services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(typeof(Catalog.Application.AssemblyMarker).Assembly));
4.2. Implementação Passo a Passo#
- Query + handler (leitura):
using MediatR;
public record GetProdutosQuery(Guid CategoriaId, bool TrackChanges)
: IRequest<IEnumerable<ProdutoDto>>;
internal sealed class GetProdutosHandler
: IRequestHandler<GetProdutosQuery, IEnumerable<ProdutoDto>>
{
private readonly IRepositoryManager _repo;
private readonly IMapper _mapper;
public GetProdutosHandler(IRepositoryManager repo, IMapper mapper)
=> (_repo, _mapper) = (repo, mapper);
public async Task<IEnumerable<ProdutoDto>> Handle(
GetProdutosQuery request, CancellationToken ct)
{
var produtos = await _repo.Produto.GetByCategoriaAsync(
request.CategoriaId, request.TrackChanges, ct);
return _mapper.Map<IEnumerable<ProdutoDto>>(produtos);
}
}
- Command + handler (escrita):
public record CreateProdutoCommand(Guid CategoriaId, ProdutoForCreationDto Produto)
: IRequest<ProdutoDto>;
internal sealed class CreateProdutoHandler
: IRequestHandler<CreateProdutoCommand, ProdutoDto>
{
private readonly IRepositoryManager _repo;
private readonly IMapper _mapper;
public CreateProdutoHandler(IRepositoryManager repo, IMapper mapper)
=> (_repo, _mapper) = (repo, mapper);
public async Task<ProdutoDto> Handle(CreateProdutoCommand request, CancellationToken ct)
{
var entity = _mapper.Map<Produto>(request.Produto);
entity.CategoriaId = request.CategoriaId;
_repo.Produto.Create(entity);
await _repo.SaveAsync();
return _mapper.Map<ProdutoDto>(entity);
}
}
- Controller com
ISender:
private readonly ISender _sender;
public ProdutosController(ISender sender) => _sender = sender;
[HttpGet]
public async Task<IActionResult> Get(Guid categoriaId, CancellationToken ct) =>
Ok(await _sender.Send(new GetProdutosQuery(categoriaId, false), ct));
[HttpPost]
public async Task<IActionResult> Post(Guid categoriaId, ProdutoForCreationDto dto, CancellationToken ct)
{
var criado = await _sender.Send(new CreateProdutoCommand(categoriaId, dto), ct);
return CreatedAtRoute("ProdutoById", new { categoriaId, id = criado.Id }, criado);
}
4.3. Executando#
dotnet run
curl http://localhost:5000/api/categorias/{catId}/produtos
5. Exemplo Completo#
Cada novo caso de uso vira um par mensagem+handler, sem inflar controllers nem serviços genéricos.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Um handler por caso de uso | Handlers gigantes com múltiplas responsabilidades |
Propagar CancellationToken ao Send | Ignorar cancelamento |
Manter commands/queries imutáveis (record) | Mensagens mutáveis compartilhadas |
AtençãoMediatR resolve handlers via DI. Se o assembly não for registrado corretamente (
RegisterServicesFromAssembly), oSendlança "handler não encontrado" em runtime.
7. Segurança e Produção#
- A validação e a autorização por caso de uso podem ser centralizadas em pipeline behaviors (próximo tópico), evitando repetição.
8. Exercícios#
- Fácil: crie uma
GetProdutoByIdQuery. - Médio: implemente
UpdateProdutoCommandeDeleteProdutoCommand. - Desafio: separe leitura e escrita em pastas/camadas distintas seguindo CQRS.
9. Resumo#
Com MediatR, cada caso de uso é uma query/command com handler dedicado; o controller só envia mensagens via ISender. Controllers ficam finos e a lógica, testável.
10. Próximos Passos#
A seguir, notifications e pipeline behaviors (validação transversal).
11. Referências#
- Documentação — MediatR
- Microsoft Learn — Padrão Mediator em microsserviços
- Microsoft Learn — Injeção de dependência