Arquitetura de Sistemas de Pedidos: Modelagem Orientada a Domínio (DDD)

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 de pedido.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.

Tags: DDD arquitetura de software java modelagem de dados microservices

Publicado em 8-4 13:30