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.
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.
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.
- Requisição
- entra pelo 1º eloAutenticação
- ok → próximo (senão 401)Rate limit
- dentro da cota → próximo (senão 429)Handler
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.
- 1
Um elo, uma responsabilidade
Cada manipulador autentica, limita ou resolve — e decide se encerra ou repassa.
- 2
Encadear na montagem
A ordem é configuração explícita: auth.encadear(rate).encadear(handler).
- 3
Entregar ao primeiro
O cliente chama só a entrada da corrente; não conhece os elos seguintes.
- 4
Fim explícito
Se ninguém tratar, o último elo (ou o fallback) responde com erro claro — nunca silêncio.
Navegue pelas camadas
Camada 1/3Cliente
use ← →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.
Estrutura
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" }));class Elo:
def __init__(self):
self._proximo = None
def encadear(self, proximo):
self._proximo = proximo
return proximo # permite encadear fluente
def tratar(self, req):
if self._proximo:
return self._proximo.tratar(req)
return {"status": 404, "corpo": "sem manipulador"}
class Autenticacao(Elo):
def tratar(self, req):
if not req.get("usuario"):
return {"status": 401, "corpo": "sem credencial"}
return super().tratar(req) # passa adiante
class RateLimit(Elo):
def __init__(self):
super().__init__()
self._vistos = {}
def tratar(self, req):
u = req["usuario"]
self._vistos[u] = self._vistos.get(u, 0) + 1
if self._vistos[u] > 100:
return {"status": 429, "corpo": "devagar"}
return super().tratar(req)
class Handler(Elo):
def tratar(self, req):
return {"status": 200, "corpo": "ok: " + req["rota"]}
auth = Autenticacao()
auth.encadear(RateLimit()).encadear(Handler())
print(auth.tratar({"usuario": "ana", "rota": "/pedidos"}))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.
Armadilhas
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.
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.
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.
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 comnext(), 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
- DecoratorAdiciona comportamento a um objeto em tempo de execução envolvendo-o em camadas empilháveis que compartilham a mesma interface — composição no lugar de herança.
- CommandTransforma uma ação em objeto, com os dados de que ela precisa. Isso permite enfileirar, agendar, registrar e desfazer operações — e desacopla quem pede de quem executa.
- FacadeOferece uma interface simples e de alto nível para um subsistema complexo, escondendo a coreografia entre as peças atrás de um único ponto de entrada.