Sobre MediatR
Publicado em: sábado, 8 de fev de 2025
Categorias: .NET

Nos últimos anos, o MediatR tornou-se amplamente utilizado na comunidade .NET. Muitos projetos são construídos a partir de templates que o incorporam, resultando em uma adoção generalizada. No entanto, poucas pessoas questionam a necessidade real de sua utilização.

Em algumas ocasiões, fui questionado sobre os motivos para não usar MediatR. Para muitos desenvolvedores, sua presença em um projeto é considerada um padrão inevitável, algo que não precisa de justificativa ou debate.

Neste artigo, quero explorar essa ferramenta sob uma ótica mais crítica e avaliar seu impacto real na arquitetura de software.

Por que usamos o MediatR?

Quando participo da definição de uma arquitetura, sempre faço a pergunta: Qual problema estamos tentando resolver com MediatR?

Se as justificativas se resumirem a respostas como:

  • “Sempre fizemos assim” / “É assim que se faz”
  • “Foi assim que aprendi“ /
  • “É assim que fulano faz“ / “É assim que fazíamos na empresa XXX” (em geral, essa resposta usada como argumento de autoridade)
  • “É um padrão amplamente adotado.”
  • “Os templates mais comuns já incluem MediatR.”

Então, é importante ficar atento, pois há fortes indícios de que o uso da biblioteca pode estar sendo motivado mais por convenção do que por necessidade técnica.

Isso não significa que o MediatR seja uma escolha ruim por si só. Meu objetivo não é criticá-lo, mas sim incentivar uma reflexão sobre seus reais benefícios e desafios. Um dos pontos que mais me preocupa é a falsa sensação de desacoplamento que sua adoção pode criar.

MediatR e o Desacoplamento

Uma das principais razões para utilizar o MediatR é a ideia de reduzir o acoplamento entre diferentes partes do código. No entanto, na maioria dos casos, ele é usado para implementar um modelo request/response, criando apenas uma camada de indireção entre quem faz a chamada e quem a atende.

O problema é que essa dependência não desaparece, apenas fica oculta. O chamador ainda depende diretamente do manipulador da requisição, mas agora através de um intermediário, o que pode adicionar complexidade sem um benefício claro.

Essa abordagem pode dificultar a leitura e manutenção do código. Com o MediatR, um simples fluxo de execução pode se tornar mais trabalhoso de entender, exigindo que os desenvolvedores naveguem entre múltiplas classes para compreender a relação entre os componentes.

Isso pode ser insignificante em aplicações pequenas, mas à medida que a complexidade cresce, essa abstração pode se tornar um obstáculo ao invés de um benefício.

Quando o MediatR é realmente útil?

O MediatR brilha quando utilizado em propagação de notificações ou publicação de eventos. Em um cenário 1 para N, onde um componente emite uma mensagem e múltiplos receptores podem agir sobre ela sem depender de respostas diretas, ele pode ser bastante útil.

Além disso, o MediatR pode ser uma solução eficiente para evitar dependências cíclicas dentro do domínio da aplicação.

Por outro lado, se o fluxo continua sendo estritamente sequencial e cada chamada depende da resposta da outra, o benefício do MediatR se torna questionável. Nesses casos, um serviço injetado diretamente pode ser uma solução mais simples, eficiente e direta.

Impacto na Performance

Outro aspecto que raramente é considerado é o impacto na performance. Embora em aplicações pequenas isso não seja um grande problema, em sistemas maiores a indireção extra pode gerar latências desnecessárias.

Cada requisição passa por um pipeline, o que pode aumentar a sobrecarga, especialmente se houver behaviors e middlewares adicionais configurados. Isso pode resultar em maior consumo de memória e pior desempenho em comparação com uma abordagem baseada em injeção de dependência direta.

Portanto, antes de adotar o MediatR, é importante avaliar se essa complexidade adicional realmente compensa em termos de manutenção e performance.

Beleza e Simplicidade

O uso do MediatR adiciona uma camada de indireção, separando a solicitação (request) de sua execução. Essa abordagem pode parecer mais verbosa à primeira vista, mas introduz um belo padrão arquitetural.

Comparação: Implementação Direta vs MediatR em Request/Response

Ao comparar uma implementação direta com uma implementação usando MediatR no padrão request/response, podemos perceber nuances que vão além da simples organização do código. Vamos ilustrar isso com um exemplo prático.

Implementação Direta:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class PedidoService
{
private readonly IPagamentoService _pagamentoService;
public PedidoService(IPagamentoService pagamentoService)
{
_pagamentoService = pagamentoService;
}
public bool ProcessarPedido(Pedido pedido)
{
return _pagamentoService.RealizarPagamento(pedido);
}
}
public class PedidoService { private readonly IPagamentoService _pagamentoService; public PedidoService(IPagamentoService pagamentoService) { _pagamentoService = pagamentoService; } public bool ProcessarPedido(Pedido pedido) { return _pagamentoService.RealizarPagamento(pedido); } }
public class PedidoService
{
    private readonly IPagamentoService _pagamentoService;

    public PedidoService(IPagamentoService pagamentoService)
    {
        _pagamentoService = pagamentoService;
    }

    public bool ProcessarPedido(Pedido pedido)
    {
        return _pagamentoService.RealizarPagamento(pedido);
    }
}

Neste caso, temos um código simples e direto. A dependência é explicitamente declarada e facilmente rastreável, tornando o código mais legível e de fácil depuração.

Por que é belo?

A beleza da implementação direta está na sua simplicidade, clareza e ausência de dúvidas. O fluxo de execução é evidente, o que facilita o entendimento e a manutenção do código. Quando um novo desenvolvedor precisa compreender a lógica, ele não precisa navegar entre múltiplas camadas ou abstrações, tornando o processo mais eficiente.

Implementação com MediatR:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class ProcessarPedidoCommand : IRequest<bool>
{
public Pedido Pedido { get; }
public ProcessarPedidoCommand(Pedido pedido) => Pedido = pedido;
}
public class ProcessarPedidoHandler : IRequestHandler<ProcessarPedidoCommand, bool>
{
private readonly IPagamentoService _pagamentoService;
public ProcessarPedidoHandler(IPagamentoService pagamentoService)
{
_pagamentoService = pagamentoService;
}
public Task<bool> Handle(ProcessarPedidoCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(_pagamentoService.RealizarPagamento(request.Pedido));
}
}
public class ProcessarPedidoCommand : IRequest<bool> { public Pedido Pedido { get; } public ProcessarPedidoCommand(Pedido pedido) => Pedido = pedido; } public class ProcessarPedidoHandler : IRequestHandler<ProcessarPedidoCommand, bool> { private readonly IPagamentoService _pagamentoService; public ProcessarPedidoHandler(IPagamentoService pagamentoService) { _pagamentoService = pagamentoService; } public Task<bool> Handle(ProcessarPedidoCommand request, CancellationToken cancellationToken) { return Task.FromResult(_pagamentoService.RealizarPagamento(request.Pedido)); } }
public class ProcessarPedidoCommand : IRequest<bool>
{
    public Pedido Pedido { get; }
    public ProcessarPedidoCommand(Pedido pedido) => Pedido = pedido;
}

public class ProcessarPedidoHandler : IRequestHandler<ProcessarPedidoCommand, bool>
{
    private readonly IPagamentoService _pagamentoService;
    
    public ProcessarPedidoHandler(IPagamentoService pagamentoService)
    {
        _pagamentoService = pagamentoService;
    }

    public Task<bool> Handle(ProcessarPedidoCommand request, CancellationToken cancellationToken)
    {
        return Task.FromResult(_pagamentoService.RealizarPagamento(request.Pedido));
    }
}

A beleza do MediatR reside na organização e padronização da comunicação entre componentes da aplicação.

É importante ressaltar que, caso seja necessário adicionar novos comportamentos, como logs, validações ou métricas, podemos fazê-lo por meio de behaviors, conseguimos fazer isso sem modificar diretamente os handlers.

O uso do MediatR adiciona uma camada de indireção. Essa abordagem pode parecer mais verbosa à primeira vista. Nesse contexto, ele reduz o acoplamento direto entre serviços, transformando acoplamento direto em indireto, quase oculto. E aqui mora minha ressalva.

MediatR continua sendo uma boa escolha, mas é preciso ter clareza sobre seus objetivos de uso no projeto.

Conclusão

O MediatR não deve ser adotado automaticamente, apenas porque é um padrão popular. Ele tem muitos méritos, mas deve ser aplicado de forma criteriosa, como praticamente tudo em nossos projetos. Antes de implementá-lo, vale a pena considerar:

  • Qual problema ele realmente resolve no contexto do projeto?
  • Ele está promovendo um desacoplamento genuíno ou apenas escondendo dependências?
  • O código ficaria mais claro e simples sem essa camada adicional?
  • O impacto na performance justifica a adoção dessa abordagem?

Ferramentas e padrões de design existem para resolver problemas, não para serem utilizados indiscriminadamente. O MediatR pode ser uma solução interessante, mas sua adoção precisa ser pensada para ser estratégica. Senão, se torna apenas mais um peso que aumenta a complexidade sem trazer benefícios.

E você? Como tem utilizado o MediatR em seus projetos? Tem encontrado mais benefícios ou desafios?

[default]

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.


4x Microsoft MVP

Categorias

Assine

[special-full-page]

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.