Domain-Driven Design: o que é e quando usar

1. Introdução ao Domain-Driven Design (DDD)

Domain-Driven Design (DDD) é uma abordagem de desenvolvimento de software que coloca o domínio do negócio no centro de todas as decisões arquiteturais. Proposto por Eric Evans em seu livro homônimo de 2003, o DDD propõe que a complexidade essencial de um sistema não está na tecnologia, mas sim nas regras e processos do negócio que ele precisa suportar.

Diferente de arquiteturas tradicionais como MVC (Model-View-Controller), que organizam o código por camadas técnicas (controladores, modelos, visões), o DDD organiza o código em torno do modelo de domínio. Enquanto no MVC o "Model" frequentemente se torna um simples reflexo do banco de dados, no DDD o modelo de domínio é rico em comportamento e regras de negócio.

2. Pilares Conceituais do DDD

Domínio e Subdomínios: Todo sistema opera em um domínio principal (core domain), que é a razão de existir do software. Ao redor dele, existem subdomínios de suporte (apoiam o core domain) e genéricos (problemas comuns já resolvidos por soluções prontas, como autenticação).

Ubiquitous Language: É o vocabulário compartilhado entre especialistas de negócio e desenvolvedores. Se o negócio chama algo de "Pedido", o código deve ter uma classe Pedido, e não Order ou SolicitacaoCompra. Essa linguagem única elimina ruídos de comunicação.

Bounded Contexts: São fronteiras explícitas onde um modelo de domínio é válido. Um "Cliente" no contexto de Vendas pode ter atributos diferentes do "Cliente" no contexto de Cobrança. Cada bounded context possui seu próprio modelo e linguagem.

text
// Exemplo de dois bounded contexts com modelos diferentes para "Cliente"

// Contexto: Vendas
public class Cliente {
    private ClienteId id;
    private String nome;
    private Endereco enderecoEntrega;
    private List<Pedido> historicoPedidos;

    public void adicionarPedido(Pedido pedido) { ... }
}

// Contexto: Cobrança
public class Cliente {
    private ClienteId id;
    private String nome;
    private String documentoFiscal;
    private List<Fatura> faturasAbertas;
    private ScoreCredito score;

    public boolean podeParcelar(double valor) { ... }
}

3. Blocos de Construção Táticos

Entidades e Value Objects: Entidades possuem identidade única e são mutáveis ao longo do tempo. Value Objects são imutáveis e definidos apenas por seus atributos. Um Preco é um Value Object; um Pedido é uma Entidade.

text
// Entidade: possui identidade (id)
public class Pedido {
    private PedidoId id;
    private List<ItemPedido> itens;
    private StatusPedido status;

    public void confirmar() {
        if (itens.isEmpty()) 
            throw new DominioException("Pedido sem itens não pode ser confirmado");
        this.status = StatusPedido.CONFIRMADO;
    }
}

// Value Object: imutável, sem identidade
public final class Preco {
    private final BigDecimal valor;
    private final String moeda;

    public Preco(BigDecimal valor, String moeda) {
        if (valor.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException("Preço não pode ser negativo");
        this.valor = valor;
        this.moeda = moeda;
    }

    public Preco somar(Preco outro) {
        if (!this.moeda.equals(outro.moeda))
            throw new IllegalArgumentException("Moedas diferentes");
        return new Preco(this.valor.add(outro.valor), this.moeda);
    }
}

Aggregates e Aggregate Roots: Um Aggregate é um cluster de objetos que devem ser tratados como uma unidade. O Aggregate Root é a única porta de entrada para modificar qualquer objeto dentro do aggregate, garantindo consistência transacional.

text
// Aggregate Root: Pedido
public class Pedido {
    private PedidoId id;
    private List<ItemPedido> itens;  // parte do aggregate

    public void adicionarItem(Produto produto, int quantidade) {
        // Regra: não pode adicionar item em pedido já confirmado
        if (status == StatusPedido.CONFIRMADO)
            throw new DominioException("Pedido já confirmado");
        itens.add(new ItemPedido(produto, quantidade));
    }
}

Repositories e Factories: Repositories abstraem a persistência, permitindo que o domínio permaneça puro. Factories encapsulam a criação de objetos complexos.

4. Domain Events e Commands

Domain Events notificam que algo relevante ocorreu no domínio. Commands representam intenções explícitas de alterar o estado.

text
// Domain Event
public class PedidoConfirmado {
    private PedidoId pedidoId;
    private LocalDateTime dataConfirmacao;
    private List<ItemPedido> itens;
}

// Command
public class ConfirmarPedidoCommand {
    private PedidoId pedidoId;
    private String usuarioResponsavel;
}

// Fluxo: Application Service orquestra
public class PedidoApplicationService {
    private PedidoRepository repositorio;
    private EventPublisher publisher;

    public void handle(ConfirmarPedidoCommand cmd) {
        Pedido pedido = repositorio.buscarPorId(cmd.pedidoId);
        pedido.confirmar();  // Domain logic pura
        repositorio.salvar(pedido);

        publisher.publicar(new PedidoConfirmado(
            pedido.getId(), LocalDateTime.now(), pedido.getItens()
        ));
    }
}

5. Serviços de Domínio e Aplicação

Domain Services contêm lógica de negócio que não se encaixa naturalmente em uma Entidade ou Value Object. Application Services orquestram casos de uso, coordenando Domain Services, Repositories e eventos.

text
// Domain Service: lógica que envolve múltiplas entidades
public class CalculadoraFrete {
    public Frete calcular(Pedido pedido, Endereco destino) {
        double pesoTotal = pedido.getItens().stream()
            .mapToDouble(i -> i.getProduto().getPeso() * i.getQuantidade())
            .sum();
        return new Frete(pesoTotal, destino.getRegiao());
    }
}

// Application Service: orquestração
public class FinalizarPedidoService {
    private PedidoRepository pedidoRepo;
    private CalculadoraFrete calculadora;

    public void executar(PedidoId id, Endereco destino) {
        Pedido pedido = pedidoRepo.buscarPorId(id);
        Frete frete = calculadora.calcular(pedido, destino);
        pedido.atribuirFrete(frete);
        pedidoRepo.salvar(pedido);
    }
}

6. Quando Aplicar DDD na Prática

Cenários ideais para DDD incluem domínios com regras de negócio complexas e mutáveis, como sistemas financeiros, seguros, logística ou saúde. Equipes maduras com conhecimento do negócio e disposição para iteração contínua são essenciais.

Quando evitar DDD: Sistemas CRUD simples (cadastros básicos), protótipos rápidos, MVPs com prazo curto ou times pequenos sem acesso a especialistas de negócio. O custo de modelagem não compensa se o domínio for trivial.

Trade-offs: DDD exige investimento inicial maior em modelagem e comunicação, mas retorna em manutenibilidade e adaptabilidade a longo prazo. Para domínios estáveis e simples, o custo supera o benefício.

7. DDD e Arquiteturas Modernas

DDD se alinha naturalmente com Clean Architecture e Hexagonal Architecture, que também pregam independência do domínio em relação a frameworks e infraestrutura.

Em microsserviços, cada bounded context tipicamente se torna um serviço independente, com seu próprio banco de dados e modelo. A comunicação entre bounded contexts ocorre via Domain Events ou APIs.

A combinação com CQRS (Command Query Responsibility Segregation) e Event Sourcing é comum em sistemas que precisam de auditoria completa e escalabilidade separada entre leitura e escrita.

text
// Exemplo: Bounded Context como microsserviço
// Serviço: Pedidos (bounded context próprio)
// Serviço: Estoque (outro bounded context)

// Comunicação via Domain Event
// PedidoConfirmado -> EstoqueService.atualizarEstoque()

8. Passos para Começar com DDD

Event Storming é uma técnica colaborativa onde equipe técnica e de negócio mapeiam eventos do domínio em post-its. Domain Storytelling usa narrativas para descobrir regras implícitas.

Comece pequeno: identifique um subdomínio core, modele um aggregate, implemente com testes de domínio. O modelo deve evoluir conforme o conhecimento do negócio aumenta — DDD é iterativo.

Ferramentas como testes de unidade focados em regras de domínio (não em infraestrutura) e refatoração contínua são práticas essenciais para manter o modelo relevante.

Referências