ComportamentalIntermediário6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Chain of Responsibility

Passa uma requisição por uma corrente de manipuladores; cada um decide se trata ou repassa adiante, sem que o remetente saiba quem vai atender.

Em 10 segundos

A requisição entra pelo primeiro elo; cada um decide se resolve ali (devolvendo 401, 429…) ou chama o próximo. Quem enviou não conhece a corrente — e reordenar as etapas é mexer na montagem, não nas etapas.

  • #middleware
  • #pipeline
  • #desacoplamento
  • #gof
Analogia

A fila do aeroporto

Do check-in até o portão você passa por vários balcões: documento, despacho de bagagem, raio-X, imigração. Cada um resolve o que é seu e libera para o próximo — ou barra você ali mesmo, e os balcões seguintes nem ficam sabendo. Nenhum atendente conhece o processo inteiro, e o aeroporto pode inserir uma checagem nova sem retreinar todos os outros.

01Conceito

O problema

Preocupações transversais se acumulam sobre o mesmo ponto de entrada: autenticar, autorizar, limitar, validar, registrar, medir. Nenhuma é a regra de negócio, mas todas precisam acontecer antes dela.

Empilhadas num método, viram uma escada de if com retornos antecipados, onde a ordem é implícita e ninguém sabe se mover uma linha quebra algo.

O ponto que separa este padrão dos vizinhos é a interrupção. No Decorator, todas as camadas costumam agir e a chamada segue até o fim. Na corrente, cada elo tem autoridade para encerrar — e é exatamente isso que o torna adequado a verificações.

Há duas variantes na prática, e confundi-las causa discussão desnecessária. A clássica do GoF procura um único responsável: alguém trata e a corrente para, como no despacho de eventos de UI que sobe pela árvore até alguém consumir. A variante middleware, muito mais comum hoje, faz todos participarem em sequência e ainda permite agir na volta — é o modelo de Express, ASP.NET Core e da maioria dos servidores HTTP.

A segunda variante é tão dominante que 'middleware' virou o nome popular do padrão. Vale saber que são o mesmo esqueleto com contratos diferentes: na clássica, repassar é a exceção; na de middleware, repassar é o caminho normal.

02Conceito

A corrente é configuração, não código

O valor do padrão aparece quando a montagem sai do código das etapas: a mesma coleção de elos serve a rotas diferentes em ordens diferentes.

Quando a corrente é montada em um ponto central, ela vira documentação executável: lendo a montagem você sabe exatamente o que acontece com uma requisição, em que ordem, sem abrir nenhum manipulador.

Isso também torna a ordem testável. Trocar rate limit e autenticação de lugar muda o comportamento de forma sutil — limitar antes de autenticar gasta cota com requisição anônima; autenticar antes gasta CPU com quem já estourou a cota. São decisões reais, e ficam explícitas na montagem.

O antipadrão correspondente é o elo que se auto-registra ou que decide sozinho quem vem depois. Aí a ordem volta a ser implícita, espalhada por N arquivos, e o benefício principal se perde.

Como se encaixa
  1. Requisição
  2. entra pelo 1º eloAutenticação
  3. ok → próximo (senão 401)Rate limit
  4. dentro da cota → próximo (senão 429)Handler
Cada elo só conhece o próximo. Qualquer um pode encerrar ali mesmo — e quando isso acontece, os elos seguintes nem são chamados.
Sem o padrão × com o padrão

Com a corrente: elos independentes

  • cada preocupação é uma classe testável isolada
  • a ordem vira montagem explícita e revisável
  • rotas diferentes compõem correntes diferentes
  • inserir uma etapa não toca nas existentes

O custo é indireção: o stack trace fica mais fundo e descobrir por que uma requisição parou exige saber qual elo a interrompeu — daí a importância de cada um dizer quem barrou.

A corrente não reduz a quantidade de verificações; ela separa cada uma e transforma a ordem, que era acidental, em decisão declarada num lugar só.
Passo a passoO fluxo, passo a passo
  1. 1

    Um elo, uma responsabilidade

    Cada manipulador autentica, limita ou resolve — e decide se encerra ou repassa.

  2. 2

    Encadear na montagem

    A ordem é configuração explícita: auth.encadear(rate).encadear(handler).

  3. 3

    Entregar ao primeiro

    O cliente chama só a entrada da corrente; não conhece os elos seguintes.

  4. 4

    Fim explícito

    Se ninguém tratar, o último elo (ou o fallback) responde com erro claro — nunca silêncio.

03Anatomia

Navegue pelas camadas

Camada 1/3Cliente

Camada 1

Cliente

entrega ao primeiro elo

Conhece só a entrada da corrente e o formato da resposta. Não sabe quantos elos existem nem qual deles vai atender — e é isso que permite mudar a composição sem tocar nele.

No código

const resposta = corrente.tratar(req);

Se violar: cliente que checa `if (resposta.status === 429)` para decidir a próxima etapa reabsorveu a lógica da corrente.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

interface Requisicao {
  usuario?: string;
  rota: string;
}
type Resposta = { status: number; corpo: string };

abstract class Elo {
  private proximo?: Elo;

  encadear(proximo: Elo): Elo {
    this.proximo = proximo;
    return proximo; // permite encadear fluente
  }

  tratar(req: Requisicao): Resposta {
    // sem proximo, a corrente acabou sem ninguem resolver
    return this.proximo
      ? this.proximo.tratar(req)
      : { status: 404, corpo: "sem manipulador" };
  }
}

class Autenticacao extends Elo {
  tratar(req: Requisicao): Resposta {
    if (!req.usuario) return { status: 401, corpo: "sem credencial" };
    return super.tratar(req); // passa adiante
  }
}

class RateLimit extends Elo {
  private vistos = new Map<string, number>();
  tratar(req: Requisicao): Resposta {
    const n = (this.vistos.get(req.usuario!) ?? 0) + 1;
    this.vistos.set(req.usuario!, n);
    if (n > 100) return { status: 429, corpo: "devagar" };
    return super.tratar(req);
  }
}

class Handler extends Elo {
  tratar(req: Requisicao): Resposta {
    return { status: 200, corpo: "ok: " + req.rota };
  }
}

const auth = new Autenticacao();
auth.encadear(new RateLimit()).encadear(new Handler());
console.log(auth.tratar({ usuario: "ana", rota: "/pedidos" }));
06No mundo real

Casos de uso

Middleware de servidor HTTP

Cenário

Toda requisição de uma API precisa de CORS, parsing do corpo, autenticação, autorização por papel, rate limit e métricas antes de chegar ao controlador.

Como o padrão entra

Cada preocupação é um middleware registrado na ordem desejada; qualquer um pode encerrar devolvendo a resposta. Rotas públicas montam uma corrente menor, sem os elos de autenticação.

Trade-off: A ordem passa a ser um detalhe crítico e silencioso: registrar métricas depois da autenticação faz você perder a medição das requisições rejeitadas, e nada no código acusa isso — só a análise do painel meses depois.

Aprovação de despesas por alçada

Cenário

Uma despesa é aprovada pelo gestor até certo valor, pelo diretor acima disso e pelo comitê financeiro em valores altos, com regras que mudam por área.

Como o padrão entra

Cada nível é um elo que aprova o que está na sua alçada e repassa o que excede. Mudar limites ou inserir um nível novo é reconfigurar a corrente, sem tocar nas regras existentes.

Trade-off: É a variante clássica, em que alguém precisa tratar — e ela permite, por construção, que a despesa chegue ao fim sem aprovador. Sem um elo final explícito de 'fora de alçada', o pedido some do fluxo sem erro.

Filtros de moderação de conteúdo

Cenário

Uma postagem passa por detecção de spam, checagem de palavras banidas, análise de imagem e revisão por reputação do autor antes de ir ao ar.

Como o padrão entra

Cada filtro é um elo que pode bloquear, sinalizar ou liberar para o próximo. Os filtros caros (análise de imagem) ficam depois dos baratos, para serem evitados quando um anterior já bloqueou.

Trade-off: A corrente para no primeiro bloqueio, então o autor recebe um motivo de cada vez e pode precisar de várias tentativas para publicar — bom para custo, ruim para experiência. Coletar todos os motivos exige abrir mão da interrupção.

A corrente em que ninguém trata

Compila, roda, passa no code review. E quebra depois.

Cada elo verifica se é o dono do pedido e, se não for, passa adiante. O último também passa adiante. O pedido chega ao fim da corrente e simplesmente desaparece — sem erro, sem log, sem resposta.

abstract class Elo {
  private proximo?: Elo;
  encadear(e: Elo) { this.proximo = e; return e; }

  tratar(p: Pedido): void {
    if (this.aceita(p)) return this.processar(p);
    this.proximo?.tratar(p);   // <- se nao houver proximo, some em silencio
  }

  protected abstract aceita(p: Pedido): boolean;
  protected abstract processar(p: Pedido): void;
}

const corrente = new EloBoleto();
corrente.encadear(new EloPix()).encadear(new EloCartao());

// Chega um pedido de pagamento em cripto.
// Nenhum elo aceita. O '?.' engole. A funcao retorna void.
// Do lado de fora, parece que deu certo.
corrente.tratar(pedidoCripto);

O que quebra

  • Com entrada inesperadaO pedido é descartado silenciosamente e quem chamou acredita que foi processado.
  • No suporteAparece como 'o pagamento sumiu': não há erro, não há log, não há registro de que passou por ali.
  • Na refatoraçãoRemover um elo que era o único a aceitar certo caso não quebra nenhum teste — só para de funcionar em produção.

A saída: A corrente precisa de um fim explícito: ou um elo terminal que trata o que ninguém quis (e registra), ou tratar devolve um resultado que distingue 'tratado' de 'ninguém tratou'. Silêncio nunca pode ser um caminho válido de saída.

08Cuidado

Armadilhas

  1. Requisição que cai no vazio

    O padrão não garante que alguém trate. Se nenhum elo assume e não existe um final explícito, a requisição termina em silêncio — e o sintoma aparece longe, como 'às vezes o pedido não aparece'. Toda corrente precisa de um fim que diga, em alto e bom som, que ninguém tratou.

  2. Elo com estado compartilhado

    Manipuladores costumam ser instanciados uma vez e usados por todas as requisições, muitas vezes em paralelo. Guardar dados da requisição atual num campo do elo (como o contador do exemplo, se fosse por requisição) vaza informação entre usuários — uma das causas mais desagradáveis de bug em produção, porque só aparece sob concorrência.

  3. Depuração às cegas

    Quando uma requisição é rejeitada, saber qual dos oito elos a barrou pode virar caça ao tesouro. Cada interrupção deveria identificar seu autor na resposta ou no log estruturado; sem isso, o desacoplamento que ajudou a escrever atrapalha a operar.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Várias etapas independentes processam a mesma requisição e qualquer uma pode interrompê-la.
  • A composição muda por contexto (rota, ambiente, plano).
  • Quem envia não deve conhecer quem trata.

Evite quando

3
  • As etapas são poucas, fixas e sempre executadas.
  • Você precisa garantir tratamento — a corrente admite que ninguém trate.
  • Você precisa de todos os motivos de rejeição de uma vez, e não do primeiro.

Testar o que ficou

3 armadilhas de Chain of Responsibility viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • A cadeia de middlewareCada elo decide se trata, se passa adiante com next(), ou se encerra a requisição ali.
  • Bubbling de evento do DOMO clique sobe do alvo até a raiz, e qualquer nó no caminho pode tratar ou parar a propagação.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Cada elo trata ou passa adiante.
auth.proximo(rateLimit).proximo(handler);

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • A cadeia adiciona saltos entre a requisição e o handler que de fato a trata
  • Uma requisição que ninguém trata some em silêncio se não houver um handler final

Não vale se: sempre se sabe de antemão quem trata a requisição — sem essa incerteza, chamar o handler certo direto é mais claro.

Onde isto aparece

Continue por aqui