Design Patterns

Chain of Responsibility

Passa uma requisição por uma cadeia de handlers, cada um decide processar ou repassar

Conceito e intenção

Chain of Responsibility passa uma requisição por uma cadeia de handlers. Cada handler decide se processa a requisição ou a repassa para o próximo handler na cadeia. O padrão desacopla o remetente dos processadores — o remetente não sabe quais handlers existem, nem qual deles vai processar a requisição.

O problema motivador é a validação ou processamento em camadas. Um pedido de compra pode precisar passar por validação de estoque, verificação de limite de crédito, análise de fraude e aprovação de gerente para pedidos acima de determinado valor. Sem Chain of Responsibility, a lógica seria um if gigante ou chamadas sequenciais explícitas acopladas. Com Chain, cada handler é independente, testável e a ordem pode ser configurada em runtime.

A diferença em relação ao Observer: Observer notifica todos os ouvintes. Chain of Responsibility para quando um handler processa a requisição (ou a rejeita). São propósitos distintos.


Estrutura

Sender ──▶ H1 ──▶ H2 ──▶ H3 ──▶ null (fim)
             processou?    processou?
             não → passa  sim → para
┌─────────────────────────────────────────┐
│   <<interface>> Handler                 │
│  + handle(request)                      │
│  + setNext(handler)                     │
└─────────────────────────────────────────┘

         ┌─────────┴────────┐
  ┌──────┴──────┐    ┌──────┴──────┐
  │ HandlerA    │    │ HandlerB    │
  │ - next      │    │ - next      │
  │ + handle()  │    │ + handle()  │
  └─────────────┘    └─────────────┘

Participantes:

  • Handler — interface com handle() e referência ao próximo handler
  • AbstractHandler — implementação base que cuida de repassar ao próximo
  • ConcreteHandler — processa ou repassa baseado em sua responsabilidade

Implementação Java — pipeline de aprovação de pedido

// Requisição que percorre a cadeia
public record OrderApprovalRequest(
    Order order,
    Customer customer,
    List<String> approvalNotes  // acumula anotações ao longo da cadeia
) {
    public OrderApprovalRequest withNote(String note) {
        List<String> newNotes = new ArrayList<>(approvalNotes);
        newNotes.add(note);
        return new OrderApprovalRequest(order, customer, Collections.unmodifiableList(newNotes));
    }
}

// Resultado da cadeia
public record ApprovalResult(
    boolean approved,
    String reason,
    List<String> notes
) {
    public static ApprovalResult approve(List<String> notes) {
        return new ApprovalResult(true, "Aprovado", notes);
    }
    public static ApprovalResult reject(String reason, List<String> notes) {
        return new ApprovalResult(false, reason, notes);
    }
}

// Handler interface
public interface OrderHandler {
    ApprovalResult handle(OrderApprovalRequest request);
    OrderHandler setNext(OrderHandler next);
}

// Base abstrata — cuida de repassar ao próximo
public abstract class AbstractOrderHandler implements OrderHandler {
    private OrderHandler next;

    @Override
    public OrderHandler setNext(OrderHandler next) {
        this.next = next;
        return next; // permite encadeamento fluente: a.setNext(b).setNext(c)
    }

    protected ApprovalResult passToNext(OrderApprovalRequest request) {
        if (next != null) return next.handle(request);
        return ApprovalResult.approve(request.approvalNotes()); // fim da cadeia = aprovado
    }
}

// Handler 1: Verificação de estoque
public class StockHandler extends AbstractOrderHandler {
    private final InventoryService inventory;

    public StockHandler(InventoryService inventory) {
        this.inventory = inventory;
    }

    @Override
    public ApprovalResult handle(OrderApprovalRequest request) {
        for (OrderItem item : request.order().getItems()) {
            if (!inventory.hasAvailable(item.getSku(), item.getQuantity())) {
                return ApprovalResult.reject(
                    "Sem estoque para: " + item.getSku(),
                    request.approvalNotes()
                );
            }
        }
        OrderApprovalRequest updated = request.withNote("Estoque verificado: OK");
        return passToNext(updated);
    }
}

// Handler 2: Limite de crédito
public class CreditLimitHandler extends AbstractOrderHandler {
    private final CustomerService customerService;

    public CreditLimitHandler(CustomerService customerService) {
        this.customerService = customerService;
    }

    @Override
    public ApprovalResult handle(OrderApprovalRequest request) {
        Money creditLimit = customerService.getCreditLimit(request.customer().getId());
        if (request.order().getTotal().isGreaterThan(creditLimit)) {
            return ApprovalResult.reject(
                String.format("Total R$%.2f excede limite de crédito R$%.2f",
                    request.order().getTotal().toDouble(), creditLimit.toDouble()),
                request.approvalNotes()
            );
        }
        return passToNext(request.withNote("Crédito verificado: OK"));
    }
}

// Handler 3: Análise de fraude
public class FraudHandler extends AbstractOrderHandler {
    private final FraudDetectionService fraudService;

    public FraudHandler(FraudDetectionService fraudService) {
        this.fraudService = fraudService;
    }

    @Override
    public ApprovalResult handle(OrderApprovalRequest request) {
        FraudScore score = fraudService.evaluate(request.order(), request.customer());
        if (score.isHighRisk()) {
            return ApprovalResult.reject(
                "Pedido suspeito — bloqueado para análise (score: " + score.getValue() + ")",
                request.approvalNotes()
            );
        }
        if (score.isMediumRisk()) {
            // Não rejeita, mas adiciona nota de alerta
            return passToNext(request.withNote("Risco médio de fraude — monitorar"));
        }
        return passToNext(request.withNote("Análise de fraude: OK"));
    }
}

// Handler 4: Aprovação gerencial para pedidos grandes
public class ManagerApprovalHandler extends AbstractOrderHandler {
    private final Money threshold;
    private final ApprovalService approvalService;

    public ManagerApprovalHandler(Money threshold, ApprovalService approvalService) {
        this.threshold       = threshold;
        this.approvalService = approvalService;
    }

    @Override
    public ApprovalResult handle(OrderApprovalRequest request) {
        if (request.order().getTotal().isLessThan(threshold)) {
            return passToNext(request); // abaixo do limite — não precisa de aprovação
        }
        // Acima do limite — solicita aprovação gerencial
        ApprovalTicket ticket = approvalService.requestApproval(
            request.order().getId(),
            request.customer().getId(),
            request.order().getTotal()
        );
        if (!ticket.isApproved()) {
            return ApprovalResult.reject(
                "Aprovação gerencial negada: " + ticket.getReason(),
                request.approvalNotes()
            );
        }
        return passToNext(request.withNote("Aprovação gerencial: " + ticket.getApproverName()));
    }
}

// Montagem da cadeia — ordem é importante e configurável
@Configuration
public class OrderHandlerConfig {
    @Bean
    public OrderHandler orderApprovalChain(
            InventoryService inventory,
            CustomerService customers,
            FraudDetectionService fraud,
            ApprovalService approval) {

        StockHandler stock          = new StockHandler(inventory);
        CreditLimitHandler credit   = new CreditLimitHandler(customers);
        FraudHandler fraudCheck     = new FraudHandler(fraud);
        ManagerApprovalHandler mgr  = new ManagerApprovalHandler(Money.of(10_000), approval);

        // Encadeamento fluente
        stock.setNext(credit).setNext(fraudCheck).setNext(mgr);

        return stock; // retorna o primeiro da cadeia
    }
}

// Uso — o chamador só conhece o início da cadeia
@Service
public class OrderApprovalService {
    private final OrderHandler approvalChain;

    public OrderApprovalService(OrderHandler approvalChain) {
        this.approvalChain = approvalChain;
    }

    public ApprovalResult approve(Order order, Customer customer) {
        OrderApprovalRequest request = new OrderApprovalRequest(order, customer, List.of());
        return approvalChain.handle(request);
    }
}

Middleware HTTP — padrão de cadeia em frameworks web

Chain of Responsibility é a base de todos os sistemas de middleware:

// Padrão de Servlet Filter (Jakarta EE)
@WebFilter("/*")
public class AuthenticationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpReq = (HttpServletRequest) request;

        String token = httpReq.getHeader("Authorization");
        if (token == null || !tokenService.isValid(token)) {
            ((HttpServletResponse) response).sendError(401, "Unauthorized");
            return; // para a cadeia
        }

        chain.doFilter(request, response); // passa para o próximo filtro
    }
}

@WebFilter("/*")
public class RateLimitFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String clientIp = request.getRemoteAddr();
        if (rateLimiter.isExceeded(clientIp)) {
            ((HttpServletResponse) response).sendError(429, "Too Many Requests");
            return; // para a cadeia
        }
        chain.doFilter(request, response);
    }
}

Variação funcional (Java 8+)

// Handler como função — sem classes concretas
@FunctionalInterface
public interface OrderValidator {
    Optional<String> validate(Order order, Customer customer);
}

public class ValidationChain {
    private final List<OrderValidator> validators = new ArrayList<>();

    public ValidationChain add(OrderValidator validator) {
        validators.add(validator);
        return this;
    }

    public ApprovalResult validate(Order order, Customer customer) {
        return validators.stream()
            .map(v -> v.validate(order, customer))
            .filter(Optional::isPresent)
            .findFirst()
            .map(err -> ApprovalResult.reject(err.get(), List.of()))
            .orElse(ApprovalResult.approve(List.of()));
    }
}

// Validadores como lambdas
ValidationChain chain = new ValidationChain()
    .add((order, customer) ->
        inventory.hasAll(order.getItems())
            ? Optional.empty()
            : Optional.of("Sem estoque"))
    .add((order, customer) ->
        order.getTotal().isLessThan(customer.getCreditLimit())
            ? Optional.empty()
            : Optional.of("Limite excedido"))
    .add((order, customer) ->
        !fraudService.evaluate(order, customer).isHighRisk()
            ? Optional.empty()
            : Optional.of("Suspeita de fraude"));

Implementação TypeScript

// Request que percorre a cadeia
interface OrderRequest {
  orderId: string;
  customerId: string;
  total: number;
  items: OrderItem[];
}

interface HandlerResult {
  approved: boolean;
  reason?: string;
  notes: string[];
}

// Handler interface
interface Handler {
  handle(request: OrderRequest): Promise<HandlerResult>;
  setNext(handler: Handler): Handler;
}

// Classe base abstrata
abstract class BaseHandler implements Handler {
  private next: Handler | null = null;

  setNext(handler: Handler): Handler {
    this.next = handler;
    return handler;
  }

  protected async passToNext(request: OrderRequest & { notes: string[] }): Promise<HandlerResult> {
    if (this.next) return this.next.handle(request);
    return { approved: true, notes: request.notes };
  }
}

// Handler concreto
class StockHandler extends BaseHandler {
  constructor(private readonly inventory: InventoryService) {
    super();
  }

  async handle(request: OrderRequest & { notes?: string[] }): Promise<HandlerResult> {
    const notes = request.notes ?? [];
    const inStock = await this.inventory.hasAll(request.items);
    if (!inStock) return { approved: false, reason: 'Sem estoque', notes };
    return this.passToNext({ ...request, notes: [...notes, 'Estoque: OK'] });
  }
}

class FraudHandler extends BaseHandler {
  constructor(private readonly fraudService: FraudService) {
    super();
  }

  async handle(request: OrderRequest & { notes?: string[] }): Promise<HandlerResult> {
    const notes = request.notes ?? [];
    const score = await this.fraudService.evaluate(request);
    if (score > 0.8) return { approved: false, reason: 'Suspeita de fraude', notes };
    return this.passToNext({ ...request, notes: [...notes, `Fraude score: ${score.toFixed(2)}`] });
  }
}

// Montagem e uso
const stockHandler = new StockHandler(inventoryService);
const fraudHandler = new FraudHandler(fraudService);

stockHandler.setNext(fraudHandler);

const result = await stockHandler.handle(orderRequest);
if (!result.approved) throw new Error(result.reason);

No mundo real

Servlet Filter Chain (FilterChain) — cada filtro é um handler que pode processar a requisição HTTP ou repassar com chain.doFilter(). Authentication, CORS, Rate Limiting, Logging são filtros típicos.

Spring Security filter chain — série de filtros (UsernamePasswordAuthenticationFilter, JwtAuthenticationFilter, ExceptionTranslationFilter) aplicados em ordem.

Express.js middlewareapp.use((req, res, next) => { ... next(); }) é Chain of Responsibility. next() repassa para o próximo handler; não chamar next() para a cadeia.

Spring MVC HandlerInterceptorpreHandle() retorna false para interromper a cadeia; true para continuar.

javax.servlet.Filter no Tomcat/Jetty — toda requisição passa pela cadeia de filtros configurada no web.xml ou por anotações.

Apache Camel Processor chains — pipelines de integração onde cada processador transforma ou roteia a mensagem.


Quando usar

  • Mais de um objeto pode processar a requisição e não é sabido a priori qual processará
  • Pipeline de validação onde múltiplas regras são aplicadas em sequência
  • Middleware HTTP ou de mensagens onde filtros podem interceptar e parar o fluxo
  • Quando a ordem dos processadores precisa ser configurada dinamicamente
  • Sistemas de autorização em camadas (autenticação → autorização → rate limiting → logging)

Quando NÃO usar

  • Quando a cadeia precisa garantir que todos os handlers sejam executados — use Observer
  • Quando a cadeia pode crescer indefinidamente sem garantia de processamento — verifique que a cadeia sempre termina
  • Quando a lógica de roteamento é simples e não precisa de extensibilidade — if/else sequencial é mais legível
  • Para cálculos onde todos os handlers contribuem ao resultado — use um aggregator/reducer em vez de chain

Observer vs Chain of Responsibility

AspectoObserverChain of Responsibility
Quem processaTodos os ouvintesUm ou nenhum handler
Pode pararNão (todos são notificados)Sim (handler para a cadeia)
AcoplamentoSubject não conhece observersHandlers conhecem o próximo
Uso típicoEventos, reações assíncronasValidação, autorização, middleware

Combinações com outros padrões

Chain of Responsibility + Command: Cada handler pode receber um Command como requisição, processá-lo ou repassar. A fila de Commands pode ser processada por uma chain.

Chain of Responsibility + Decorator: Ambos envolvem sequências de processamento, mas Chain pode interromper o fluxo; Decorator sempre chama o próximo. Em middlewares HTTP, a linha é tênue — alguns frameworks usam Decorator (sempre executa), outros usam Chain (pode parar).

Chain of Responsibility + Template Method: O handle() da classe base pode ser um Template Method que define o esqueleto: verificar condição, processar se aplicável, repassar se não. Subclasses implementam apenas a condição e o processamento específico.

Chain of Responsibility + Composite: Uma “chain” pode conter nós que são eles mesmos chains menores — util para agrupar validações relacionadas em subgrupos.