Backend
Construindo um Pipeline de Processamento de Pedidos com o Padrão Chain of Responsibility em Java
Dherick Dev.to (EN Zone)
1 views
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:
Validação de estoque — os itens estão disponíveis?
Validação de pagamento — o pagamento foi aprovado?
Cálculo de frete — qual será o custo do envio?
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 entender o padrão, podemos separar a representação em duas partes:
Diagrama de classes: mostra como os objetos e handlers estão estruturados.
Desenho da arquitetura: mostra como o pipeline se relaciona com a API e os serviços externos.
Diagrama de Classes (UML)
O PedidoHandler define a estrutura comum da cadeia. Cada handler concreto herda dessa classe e implementa sua própria etapa do processamento.
classDiagram
direction LR
class PedidoHandler {
<<abstract>>
-PedidoHandler proximo
+definirProximo(PedidoHandler) PedidoHandler
+processar(Pedido) void
#continuar(Pedido) void
}
class ValidacaoEstoqueHandler {
+processar(Pedido) void
}
class ValidacaoPagamentoHandler {
+processar(Pedido) void
}
class CalculoFreteHandler {
+processar(Pedido) void
}
class ConfirmacaoPedidoHandler {
+processar(Pedido) void
}
class Pedido {
-String id
-List~String~ itens
-double valorTotal
-String status
}
PedidoHandler <|-- ValidacaoEstoqueHandler
PedidoHandler <|-- ValidacaoPagamentoHandler
PedidoHandler <|-- CalculoFreteHandler
PedidoHandler <|-- ConfirmacaoPedidoHandler
PedidoHandler --> Pedido : processa
PedidoHandler --> PedidoHandler : próximo
Como interpretar o diagrama
PedidoHandler é a classe abstrata que define o comportamento comum da cadeia.
Cada handler concreto representa uma etapa específica do processamento.
O atributo proximo mantém a referência para o próximo handler.
O método continuar() encaminha o pedido para a próxima etapa.
Pedido representa a solicitação que percorre toda a cadeia.
A cadeia pode ser montada da seguinte maneira:
PedidoHandler
│
├── ValidacaoEstoqueHandler
│
├── ValidacaoPagamentoHandler
│
├── CalculoFreteHandler
│
└── ConfirmacaoPedidoHandler
E, durante a execução:
Pedido
│
▼
Estoque
│
▼
Pagamento
│
▼
Frete
│
▼
Confirmação
Desenho da Arquitetura
O desenho arquitetural apresenta uma visão mais próxima de uma aplicação real.
O cliente não precisa conhecer cada serviço individualmente. Ele envia o pedido para a API de Pedidos, que inicia o pipeline.
Cada handler executa sua responsabilidade e pode consultar um serviço externo quando necessário.
flowchart LR
A[Cliente] --> B[API de Pedidos]
subgraph P[Pipeline de Processamento]
direction LR
C[Estoque] --> D[Pagamento]
D --> E[Frete]
E --> F[Confirmação]
end
B --> C
C -.-> G[(Serviço de Estoque)]
D -.-> H[(Gateway de Pagamento)]
E -.-> I[(Transportadora)]
F -.-> J[(Banco de Pedidos)]
F -.-> K[[Evento Pedido Confirmado]]
Funcionamento da arquitetura
A arquitetura pode ser dividida em quatro partes:
1. Entrada
O cliente envia uma solicitação para a API de Pedidos.
2. Orquestração
A API inicia a cadeia na ordem definida:
Estoque → Pagamento → Frete → Confirmação
3. Processamento
Cada handler executa apenas a responsabilidade correspondente.
Se uma etapa falhar, ela pode interromper o processamento e impedir que o pedido avance.
4. Saídas
Durante o processamento, os handlers podem consultar serviços externos.
Ao final, o sistema pode persistir o pedido e publicar um evento de confirmação.
O principal objetivo dessa arquitetura é evitar que todas as regras fiquem concentradas em uma única classe.
A API conhece a cadeia, enquanto cada handler concentra apenas a responsabilidade da sua própria etapa.
Fluxo de Sucesso
Quando todas as etapas são executadas com sucesso, o pedido percorre todo o pipeline:
Cliente
│
▼
API de Pedidos
│
▼
Validação de Estoque
│
▼
Validação de Pagamento
│
▼
Cálculo de Frete
│
▼
Confirmação
│
▼
Pedido Confirmado
Fluxo de Falha
Agora imagine que o pagamento seja recusado.
Nesse caso, o pipeline não precisa continuar:
Cliente
│
▼
API de Pedidos
│
▼
Validação de Estoque
│
▼
Validação de Pagamento
│
✕
▼
Pedido Rejeitado
Nesse cenário, o CalculoFreteHandler e o ConfirmacaoPedidoHandler não são executados.
Essa interrupção antecipada é uma das principais características do padrão quando aplicado a pipelines.
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);
}
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;
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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()
);
}
}
A cadeia criada pelo código é:
estoque
↓
pagamento
↓
frete
↓
confirmacao
A execução começa apenas no primeiro handler:
estoque.processar(pedido);
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
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
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!
Read original: https://dev.to/xdherick/construindo-um-pipeline-de-processamento-de-pedidos-com-o-padrao-chain-of-responsibility-em-java-510g
Related
The Matrix of HFT: Unpacking the Hype
Backend
0
Dev.to (EN Zone)
Does Rust Support Inheritance? Yes, No, and Maybe, All in the Same File
Backend
1
Dev.to (EN Zone)
Built a self-hosted server control panel with Laravel + Livewire (broker/privilege-separation architecture)
Backend
3
Reddit r/php
Context aware Spring AI chat microservice
Backend
4
DEV Community
Comments0
No comments yet — be the first