No desenvolvimento backend, lidar com sistemas de pedidos é um desafio quase inevitável. À primeira vista, o fluxo parece linear: criação, pagamento, envio e conclusão. No entanto, desenvolvedores que operam sistemas em produção sabem que a realidade é composta por uma malha complexa de estados e regras de negócio que dificilmente se resumem a um simples CRUD.
Ao negligenciar a modelagem, surgem problemas crônicos:
- Explosão de estados: Pendente, Processando, Pago, Enviado, Extraviado, Devolvido, Cancelado.
- Lógica fragmentada: Alterações de status espalhadas por Controllers, Services e Tasks agendadas.
- Acoplamento excessivo: O módulo de pedidos tentando gerenciar estoque, logística e promoções simultaneamente.
- Inconsistência de dados: Transições de estado inválidas que corrompem o fluxo do negócio.
Muitas vezes, a solução imediata é implementar máquinas de estado complexas ou transações distribuídas. Contudo, se o modelo de domínio estiver incorerto, essas ferramentas servem apenas como paliativos. O foco deve estar na modelagem correta do domínio.
O Pedido como um Agregado
A essência de um sistema de pedidos não é o armazenamento de dados, mas a garantia das regras de negócio. Um pedido deve ser encarado como um Agregado (Aggregate), onde ele atua como a raiz que protege a integridade de seus componentes inetrnos e estados.
Um erro comum é tratar o pedido como uma mera representação de uma tabela no banco de dados:
CREATE TABLE tb_pedidos (
id UUID PRIMARY KEY,
situacao INT,
valor_total DECIMAL(10,2)
);
No código, isso se traduz em manipulações aêmicas:
// Abordagem procedural e frágil
pedido.setSituacao(3); // O que o número 3 significa? Quais as regras para chegar aqui?
Transições de Estado Baseadas em Comportamento
Em vez de permitir que qualquer componente altere o status do pedido, o modelo deve expor métodos que representem ações de negócio reais. Isso encapsula a lógica de validação dentro da própria entidade.
public class Pedido {
private StatusPedido status;
private List<ItemPedido> itens;
public void confirmarPagamento() {
if (this.status != StatusPedido.AGUARDANDO_PAGAMENTO) {
throw new DomainException("Pagamento só pode ser confirmado para pedidos pendentes.");
}
this.status = StatusPedido.PAGO;
}
public void despachar() {
if (this.status != StatusPedido.PAGO) {
throw new DomainException("Pedido deve estar pago para ser despachado.");
}
this.status = StatusPedido.EM_TRANSITO;
}
public void cancelar() {
if (this.status == StatusPedido.ENTREGUE) {
throw new DomainException("Não é possível cancelar um pedido já entregue.");
}
this.status = StatusPedido.CANCELADO;
}
}
Separação de Responsabilidades e Desacoplamento
Um sistema de pedidos robusto não deve assumir a responsabilidade de outros domínios. A interação entre diferentes contextos deve ser feita de forma clara, preferencialmente através de eventos.
| Contexto | Responsabilidade Principal |
|---|---|
| Vendas/Pedido | Gestão do ciclo de vida e estados do pedido. |
| Estoque | Reserva e baixa de produtos. |
| Pagamento | Integração com gateways e conciliação financeira. |
| Logística | Rastreamento e entrega física. |
Ao processar um pagamento, o serviço de pedido não precisa chamar diretamente o estoque. Ele altera seu estado interno e dispara um evento de domínio:
public void processarPagamento(UUID pedidoId) {
Pedido pedido = pedidoRepository.buscarPorId(pedidoId);
// Altera o estado interno seguindo as regras de negócio
pedido.confirmarPagamento();
pedidoRepository.salvar(pedido);
// Notifica outros domínios de forma assíncrona
eventPublisher.publish(new PedidoPagoEvent(pedidoId));
}
Diretrizes para Modelagem
- Mantenha os Agregados pequenos: Inclua apenas o que é essencial para a consistência imediata. Informações detalhadas de entrega ou histórico de auditoria podem residir em outras tabelas ou contextos.
- Comportamento sobre Dados: Sempre prefira métodos como
pedido.finalizar()em vez depedido.setStatus(). - Consistência Eventual: Aceite que o estoque ou a logística podem ser atualizados segundos depois da confirmação do pedido através de mensageria.
- Valide na Entrada: Use o modelo para garantir que o sistema nunca entre em um estado inválido.