Software · Design Patterns

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.

Diagrama conceitual dos padrões comportamentais Mediator e Chain of Responsibility ilustrando a orquestração de mensagens por pipelines
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:

  1. 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 implementa IRequestHandler.
  2. 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.
  3. 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, Handler e Result isolados pode adicionar burocracia sem nenhum ganho real de desacoplamento.
Explorar este tema no Grafo de Conhecimento