DEV Community

Dherick
Dherick

Posted on

Construindo um Pipeline de Processamento de Pedidos com o Padrão Chain of Responsibility em Java

1. Base Fundamental

Introdução

O Chain of Responsibility (Cadeia de Responsabilidade) é um padrão de projeto da categoria Comportamental, catalogado pela Gang of Four (GoF).

Ele permite que uma solicitação percorra uma cadeia de handlers, onde cada um decide se processa a solicitação, a repassa para o próximo handler ou interrompe o fluxo.

Na prática, o padrão é muito útil para representar pipelines de processamento em etapas, nos quais uma operação precisa passar por várias regras antes de ser concluída.

O Problema

Sistemas que processam uma entrada por múltiplas etapas sequenciais — validação, transformação, enriquecimento e persistência — podem acabar concentrando todas essas responsabilidades em um único método.

Um exemplo seria um método processarPedido() responsável por:

  • validar estoque;
  • validar pagamento;
  • calcular frete;
  • confirmar o pedido;
  • persistir os dados;
  • publicar eventos.

Com o crescimento da aplicação, esse método pode ficar cheio de if/else e assumir responsabilidades demais.

Isso traz alguns problemas:

  • Baixa coesão: um único método concentra responsabilidades diferentes.
  • Dificuldade de manutenção: alterar uma etapa pode exigir mudanças em um bloco grande de código.
  • Dificuldade de reordenar etapas: mudar a sequência do processamento fica mais trabalhoso.
  • Pouco reaproveitamento: uma etapa específica fica difícil de utilizar de forma isolada.

O Chain of Responsibility resolve esse problema dividindo o fluxo em handlers independentes, cada um responsável por uma etapa específica.

Conceito

Uma forma simples de entender o padrão é imaginar uma linha de produção.

A peça passa pela estação de montagem, depois pela pintura e, por fim, pela inspeção de qualidade. Cada estação realiza apenas o seu trabalho e passa a peça para a próxima.

Se uma estação encontrar um problema que impeça o processo de continuar, a linha pode ser interrompida naquele ponto.

No software, cada estação representa um Handler.

O handler executa sua responsabilidade e, caso tudo esteja correto, encaminha a solicitação para o próximo elemento da cadeia.


2. Desenvolvimento

Cenário do Mundo Real

Um cenário clássico para aplicar o padrão é o pipeline de processamento de um pedido em um e-commerce.

Quando um cliente finaliza uma compra, o pedido precisa passar por algumas etapas antes de ser confirmado:

  1. Validação de estoque — os itens estão disponíveis?
  2. Validação de pagamento — o pagamento foi aprovado?
  3. Cálculo de frete — qual será o custo do envio?
  4. Confirmação do pedido — persistir o pedido e publicar o evento de confirmação.

Se qualquer etapa falhar, o pipeline deve parar imediatamente e impedir que as próximas etapas sejam executadas.


Representação Visual

Para visualizar o padrão, podemos separar a representação em duas partes: o diagrama de classes, que mostra a estrutura dos handlers, e o desenho da arquitetura, que mostra como o pedido percorre o sistema.

Diagrama de Classes (UML)

O PedidoHandler é a classe abstrata que define o comportamento comum da cadeia. Os handlers concretos herdam dessa classe e implementam cada etapa do processamento.

                         ┌──────────────────────────────┐
                         │       PedidoHandler          │
                         │          «abstract»          │
                         ├──────────────────────────────┤
                         │ - proximo : PedidoHandler    │
                         ├──────────────────────────────┤
                         │ + definirProximo()           │
                         │ + processar(Pedido)          │
                         │ # continuar(Pedido)          │
                         └───────────────┬──────────────┘
                                         │
                 ┌───────────────────────┼───────────────────────┐
                 │                       │                       │
                 ▼                       ▼                       ▼
    ┌─────────────────────┐  ┌─────────────────────┐  ┌─────────────────────┐
    │ ValidacaoEstoque    │  │ ValidacaoPagamento  │  │ CalculoFrete        │
    │      Handler        │  │       Handler       │  │      Handler         │
    ├─────────────────────┤  ├─────────────────────┤  ├─────────────────────┤
    │ + processar()       │  │ + processar()       │  │ + processar()       │
    └─────────────────────┘  └─────────────────────┘  └─────────────────────┘
                                         │
                                         ▼
                              ┌─────────────────────┐
                              │ ConfirmacaoPedido   │
                              │       Handler       │
                              ├─────────────────────┤
                              │ + processar()       │
                              └─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Cada handler representa uma responsabilidade específica dentro do pipeline:

PedidoHandler
     │
     ├── ValidacaoEstoqueHandler
     │
     ├── ValidacaoPagamentoHandler
     │
     ├── CalculoFreteHandler
     │
     └── ConfirmacaoPedidoHandler
Enter fullscreen mode Exit fullscreen mode

A referência para o próximo handler é utilizada durante a execução da cadeia. Assim, cada etapa pode concluir seu processamento e encaminhar o pedido para a próxima.


Desenho da Arquitetura

O desenho arquitetural apresenta uma visão simplificada do sistema e mostra como o pedido entra na aplicação, percorre o pipeline e interage com os serviços necessários.

                         ┌──────────────┐
                         │    Cliente   │
                         └──────┬───────┘
                                │
                                ▼
                      ┌──────────────────┐
                      │ API de Pedidos   │
                      └────────┬─────────┘
                               │
                               ▼
                  ┌──────────────────────────┐
                  │ Validação de Estoque     │
                  └────────────┬─────────────┘
                               │
                               ▼
                  ┌──────────────────────────┐
                  │ Validação de Pagamento   │
                  └────────────┬─────────────┘
                               │
                               ▼
                  ┌──────────────────────────┐
                  │ Cálculo de Frete         │
                  └────────────┬─────────────┘
                               │
                               ▼
                  ┌──────────────────────────┐
                  │ Confirmação do Pedido    │
                  └────────────┬─────────────┘
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
             ┌──────────────┐     ┌──────────────────┐
             │ Banco de     │     │ Evento: Pedido   │
             │ Pedidos      │     │ Confirmado       │
             └──────────────┘     └──────────────────┘
Enter fullscreen mode Exit fullscreen mode

Integrações externas

Durante o processamento, cada etapa pode consultar o serviço necessário para realizar sua responsabilidade:

┌──────────────────────────┐
│ Validação de Estoque     │
└────────────┬─────────────┘
             │
             ▼
┌──────────────────────────┐
│ Serviço de Estoque       │
└──────────────────────────┘


┌──────────────────────────┐
│ Validação de Pagamento   │
└────────────┬─────────────┘
             │
             ▼
┌──────────────────────────┐
│ Gateway de Pagamento     │
└──────────────────────────┘


┌──────────────────────────┐
│ Cálculo de Frete         │
└────────────┬─────────────┘
             │
             ▼
┌──────────────────────────┐
│ Serviço de Frete         │
└──────────────────────────┘


┌──────────────────────────┐
│ Confirmação do Pedido    │
└────────────┬─────────────┘
             │
             ├──────────────► Banco de Pedidos
             │
             └──────────────► Evento Pedido Confirmado
Enter fullscreen mode Exit fullscreen mode

O ponto principal da arquitetura é que a API de Pedidos inicia o pipeline, enquanto cada handler fica responsável apenas pela sua própria etapa.

Funcionamento do Pipeline

Em um cenário de sucesso, o pedido percorre todas as etapas:

Cliente
   │
   ▼
API de Pedidos
   │
   ▼
Estoque
   │
   ▼
Pagamento
   │
   ▼
Frete
   │
   ▼
Confirmação
   │
   ▼
Pedido confirmado
Enter fullscreen mode Exit fullscreen mode

Se uma etapa falhar, o processamento pode ser interrompido:

Cliente
   │
   ▼
API de Pedidos
   │
   ▼
Estoque
   │
   ▼
Pagamento
   │
   ✕
   │
   ▼
Pedido rejeitado
Enter fullscreen mode Exit fullscreen mode

Nesse caso, as etapas seguintes não precisam ser executadas. Essa interrupção antecipada é uma das principais vantagens do Chain of Responsibility aplicado a pipelines de processamento.


Implementação em Java

Handler abstrato

O PedidoHandler representa a estrutura base de todos os elementos da cadeia.

public abstract class PedidoHandler {

    protected PedidoHandler proximo;

    public PedidoHandler definirProximo(PedidoHandler proximo) {
        this.proximo = proximo;
        return proximo;
    }

    protected void continuar(Pedido pedido) {
        if (proximo != null) {
            proximo.processar(pedido);
        }
    }

    public abstract void processar(Pedido pedido);
}
Enter fullscreen mode Exit fullscreen mode

O atributo proximo guarda a referência para o próximo handler.

O método continuar() verifica se existe uma próxima etapa e, caso exista, encaminha o pedido para ela.

Classe Pedido

A classe Pedido representa o objeto que percorre toda a cadeia.

import java.util.List;

public class Pedido {

    private final String id;
    private final List<String> itens;
    private final double valorTotal;
    private String status;

    public Pedido(String id, List<String> itens, double valorTotal) {
        this.id = id;
        this.itens = itens;
        this.valorTotal = valorTotal;
        this.status = "RECEBIDO";
    }

    public String getId() {
        return id;
    }

    public List<String> getItens() {
        return itens;
    }

    public double getValorTotal() {
        return valorTotal;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
Enter fullscreen mode Exit fullscreen mode

Handler de validação de estoque

O primeiro handler verifica se todos os itens do pedido estão disponíveis.

public class ValidacaoEstoqueHandler extends PedidoHandler {

    private final EstoqueService estoqueService;

    public ValidacaoEstoqueHandler(EstoqueService estoqueService) {
        this.estoqueService = estoqueService;
    }

    @Override
    public void processar(Pedido pedido) {

        for (String item : pedido.getItens()) {

            if (!estoqueService.temEstoque(item)) {

                pedido.setStatus("REJEITADO_SEM_ESTOQUE");

                System.out.printf(
                    "[Estoque] Pedido %s rejeitado: item %s indisponivel%n",
                    pedido.getId(),
                    item
                );

                return;
            }
        }

        System.out.printf(
            "[Estoque] Pedido %s: estoque validado%n",
            pedido.getId()
        );

        continuar(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

Caso algum item não esteja disponível, o método utiliza return e interrompe a cadeia.

Caso contrário, continuar(pedido) encaminha o pedido para o próximo handler.

Handler de validação de pagamento

O segundo handler verifica se o pagamento pode ser autorizado.

public class ValidacaoPagamentoHandler extends PedidoHandler {

    private final GatewayPagamento gatewayPagamento;

    public ValidacaoPagamentoHandler(
        GatewayPagamento gatewayPagamento
    ) {
        this.gatewayPagamento = gatewayPagamento;
    }

    @Override
    public void processar(Pedido pedido) {

        boolean aprovado =
            gatewayPagamento.autorizar(pedido.getValorTotal());

        if (!aprovado) {

            pedido.setStatus("REJEITADO_PAGAMENTO");

            System.out.printf(
                "[Pagamento] Pedido %s rejeitado: pagamento nao autorizado%n",
                pedido.getId()
            );

            return;
        }

        System.out.printf(
            "[Pagamento] Pedido %s: pagamento aprovado%n",
            pedido.getId()
        );

        continuar(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

Se o pagamento for recusado, a cadeia é interrompida.

Se for aprovado, o pedido segue para o cálculo de frete.

Handler de cálculo de frete

O terceiro handler calcula o frete do pedido.

public class CalculoFreteHandler extends PedidoHandler {

    private final ServicoFrete servicoFrete;

    public CalculoFreteHandler(ServicoFrete servicoFrete) {
        this.servicoFrete = servicoFrete;
    }

    @Override
    public void processar(Pedido pedido) {

        double frete = servicoFrete.calcular(pedido);

        System.out.printf(
            "[Frete] Pedido %s: frete calculado em R$ %.2f%n",
            pedido.getId(),
            frete
        );

        continuar(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

Depois do cálculo, o pedido é encaminhado para a etapa final.

Handler de confirmação

O último handler confirma o pedido.

public class ConfirmacaoPedidoHandler extends PedidoHandler {

    @Override
    public void processar(Pedido pedido) {

        pedido.setStatus("CONFIRMADO");

        System.out.printf(
            "[Confirmacao] Pedido %s confirmado com sucesso%n",
            pedido.getId()
        );

        continuar(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

Como esse é o último elemento da cadeia, não existe outro handler para executar depois dele.


Montando a cadeia

Agora precisamos criar os handlers e definir a ordem de execução.

import java.util.List;

public class Main {

    public static void main(String[] args) {

        Pedido pedido = new Pedido(
            "PED-501",
            List.of("Teclado", "Mouse"),
            349.90
        );

        PedidoHandler estoque =
            new ValidacaoEstoqueHandler(new EstoqueService());

        PedidoHandler pagamento =
            new ValidacaoPagamentoHandler(new GatewayPagamento());

        PedidoHandler frete =
            new CalculoFreteHandler(new ServicoFrete());

        PedidoHandler confirmacao =
            new ConfirmacaoPedidoHandler();

        estoque.definirProximo(pagamento);
        pagamento.definirProximo(frete);
        frete.definirProximo(confirmacao);

        estoque.processar(pedido);

        System.out.println(
            "Status final: " + pedido.getStatus()
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

A cadeia criada pelo código é:

estoque
   ↓
pagamento
   ↓
frete
   ↓
confirmacao
Enter fullscreen mode Exit fullscreen mode

A execução começa apenas no primeiro handler:

estoque.processar(pedido);
Enter fullscreen mode Exit fullscreen mode

A partir daí, cada handler decide se deve ou não chamar o próximo.

Isso é justamente o comportamento central do Chain of Responsibility.


Prós e Contras

Vantagens

  • Responsabilidade única: cada handler cuida de uma etapa específica.
  • Facilidade de testes: cada etapa pode ser testada de forma isolada.
  • Flexibilidade: é possível adicionar, remover ou reordenar etapas.
  • Interrupção antecipada: o processamento pode parar assim que uma regra falhar.
  • Menor acoplamento: cada etapa não precisa conhecer a implementação das outras.

Trade-offs

  • Fluxo distribuído: para entender o processamento completo, é necessário observar a montagem da cadeia.
  • Risco de interrupção silenciosa: esquecer de chamar continuar(pedido) pode impedir o restante do pipeline de executar.
  • Debug mais trabalhoso: acompanhar a execução pode exigir navegar por várias classes.
  • Tratamento incompleto: é importante definir o que acontece quando uma solicitação chega ao final da cadeia sem ser processada.

3. Conclusão

Resumo

O Chain of Responsibility transforma um fluxo de processamento que poderia acabar concentrado em um método cheio de condicionais em uma cadeia de handlers independentes.

No exemplo apresentado, cada etapa possui uma responsabilidade clara:

Estoque
   ↓
Pagamento
   ↓
Frete
   ↓
Confirmação
Enter fullscreen mode Exit fullscreen mode

Essa separação permite evoluir cada parte do processo sem precisar concentrar todas as regras em uma única classe.

Além disso, o pipeline pode ser interrompido assim que uma etapa identificar um problema, evitando processamento desnecessário.

Visão Pessoal

O que mais me chamou atenção ao trabalhar com esse padrão foi como ele torna o fluxo de negócio explícito no código.

Só de olhar para a montagem da cadeia:

estoque → pagamento → frete → confirmação
Enter fullscreen mode Exit fullscreen mode

já é possível entender a ordem do processo sem precisar conhecer toda a implementação interna de cada etapa.

Para mim, esse é o principal ponto do Chain of Responsibility: ele não elimina a complexidade do processo, mas distribui essa complexidade em responsabilidades menores e organizadas.

Quando um pipeline começa a crescer, essa separação pode fazer bastante diferença na manutenção e na evolução do sistema.

E você?

Você já teve que lidar com um método gigante que fazia validação, cálculo e persistência tudo junto?

Como resolveria isso no seu projeto: Chain of Responsibility ou outra abordagem?

Comenta aí embaixo!

Top comments (0)