Padrões Comportamentais: Mediator e Chain of Responsibility no Desacoplamento de Casos de Uso
Como os padrões Mediator e Chain of Responsibility eliminam dependências caóticas entre serviços em memória, viabilizando pipelines modulares de handlers e comportamentos transversais com bibliotecas como MediatR.
Neste artigo
À medida que uma base de código corporativa se expande, o padrão comum de injeção direta de dependências nos controladores de API ou serviços de aplicação tende a degradar. É frequente encontrar construtores de classes que exigem dez ou doze interfaces injetadas (IOrderRepository, IEmailService, IInventoryClient, IPaymentGateway, IAuditLogger, ICacheManager). Esse fenômeno, conhecido como injeção de dependência inflada, revela alto acoplamento e desrespeito ao princípio da responsabilidade única.
Os padrões comportamentais catalogados pelo Gang of Four (GoF) têm como propósito organizar como os objetos colaboram e distribuem responsabilidades. Entre eles, o Mediator e o Chain of Responsibility oferecem uma solução elegante para decompor fluxos procedurais monolíticos em sequências previsíveis, testáveis e extensíveis de processamento em memória.
O Padrão Mediator: Desfazendo Teias Caóticas
O objetivo primário do Mediator é transformar uma teia de relacionamentos de muitos-para-muitos ($M \times N$) em uma topologia estelar centralizada. Em vez de componentes conversarem diretamente entre si, cada componente conhece exclusivamente a interface do mediador.
flowchart LR
subgraph Acoplamento["Sem Mediator (Dependências Caóticas)"]
direction TB
A1["Componente A"] <--> B1["Componente B"]
A1 <--> C1["Componente C"]
B1 <--> C1
B1 <--> D1["Componente D"]
C1 <--> D1
end
subgraph Estelar["Com Mediator (Topologia Estelar)"]
direction TB
A2["Componente A"] --> Med["MEDIATOR CENTRAL"]
B2["Componente B"] --> Med
C2["Componente C"] --> Med
D2["Componente D"] --> Med
Med -.-> A2
Med -.-> B2
Med -.-> C2
Med -.-> D2
end
Quando aplicado à camada de backend moderna em linguagens como C# (.NET) ou Java/Kotlin (Spring), o Mediator atua como um despachante de comandos e consultas em memória (in-process message dispatcher). O controlador HTTP recebe um payload JSON, mapeia-o para um objeto fortemente tipado de comando (ex: CreateOrderCommand) e o entrega ao mediador. O controlador desconhece inteiramente qual classe concreta executará a ação, qual banco de dados será modificado ou quais eventos serão gerados.
Chain of Responsibility: Cadeias Modulares de Execução
O padrão Chain of Responsibility (Cadeia de Responsabilidade) desacopla o remetente de uma solicitação dos seus destinatários, dando a mais de um objeto a oportunidade de tratar a solicitação. Esses objetos são encadeados em sequência e transmitem a solicitação ao longo da linha até que um ou mais deles a processem ou interrompam o fluxo.
Na prática de arquiteturas em camadas, esse padrão viabiliza a implementação de Pipeline Behaviors (ou Interceptors), que envolvem a execução de um caso de uso com preocupações transversais (cross-cutting concerns):
flowchart TD
Request["Comando de Entrada: CadastrarClienteCommand"] --> Step1["1. LoggingBehavior: Registra início da operação com Correlation ID"]
Step1 --> Step2["2. ValidationBehavior: Executa regras do FluentValidation"]
Step2 --> Decision{"Dados Válidos?"}
Decision -->|"Não (Erro 400)"| ShortCircuit["Curto-Circuito: Lança ValidationException sem tocar no banco"]
Decision -->|"Sim"| Step3["3. TransactionBehavior: Abre transação no banco de dados"]
Step3 --> Handler["4. Handler de Domínio: Executa a lógica de negócio do caso de uso"]
Handler --> Commit["Confirma Transação (Commit)"]
Commit --> Response["Retorna Resposta Limpa para o Cliente"]
A Abordagem Prática com MediatR no Ecossistema .NET
A biblioteca de código aberto MediatR popularizou a união sinérgica entre o padrão Mediator e a Cadeia de Responsabilidade no ecossistema .NET, tornando-se uma peça fundamental para implementações limpas de CQRS e Vertical Slice Architecture.
O fluxo opera através de contratos claros:
IRequest<TResponse>: define a mensagem com sua intenção e tipo de retorno;IRequestHandler<TRequest, TResponse>: implementa a execução isolada daquele caso de uso específico;IPipelineBehavior<TRequest, TResponse>: intercepta a chamada antes e depois de atingir o handler.
Exemplo de um comportamento transversal de validação automática sem contaminar os casos de uso:
public class ValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
// Passa para o próximo interceptor da cadeia ou para o handler final
return await next();
}
}
Qualquer novo comando adicionado ao sistema que possua um validador registrado herdará automaticamente a verificação de integridade sem que uma única linha de código extra precise ser escrita no controlador ou no serviço de negócio.
Matriz Comparativa: Despacho em Memória vs Comunicação Distribuída
É fundamental discernir quando empregar mediação em memória e quando adotar tecnologias distribuídas:
| Critério de Arquitetura | Mediator em Memória (ex: MediatR) | Injeção Direta de Serviços (Services) | Message Broker Distribuído (RabbitMQ / Kafka) |
|---|---|---|---|
| Fronteira de Execução | Estritamente in-process (mesmo processo e máquina) | Estritamente in-process (mesmo processo e máquina) | Out-of-process (rede remota entre servidores) |
| Acoplamento Temporal | Síncrono (bloqueia aguardando Task assíncrona) | Síncrono (chamada direta de método) | Totalmente assíncrono (desacoplado no tempo) |
| Persistência de Mensagem | Volátil (se o processo reiniciar, perde o comando) | Não aplicável | Durável (mensagens gravadas em disco/partição) |
| Tratamento de Falhas | Exceções de runtime no fluxo da thread | Exceções diretas | Dead Letter Queues (DLQ), retentativas com backoff |
| Cenário Ideal | CQRS, isolamento de casos de uso na Clean Architecture | Operações simples sem preocupações transversais | Integração orientada a eventos entre Microsserviços |
Cuidados e Armadilhas Arquiteturais
Apesar de suas virtudes, a adoção indiscriminada do Mediator acarreta custos de manutenibilidade que precisam ser pesados pela liderança técnica:
- Navegação Indireta de Código: A navegação por atalhos de IDE (Find Usages ou Go to Definition) aponta para a interface
IMediator.Send(), exigindo que o desenvolvedor busque manualmente a classe correspondente que implementaIRequestHandler. - God Mediator: Evite concentrar lógica de negócio dentro de classes mediadoras concretas. O mediador deve permanecer um despachante burro (dumb dispatcher) que apenas localiza e invoca os manipuladores adequados.
- Overengineering em CRUDs Triviais: Em domínios onde uma operação limita-se a ler uma tabela e retorná-la sem transformações, a cerimônia de criar
Query,HandlereResultisolados pode adicionar burocracia sem nenhum ganho real de desacoplamento.