Design Orientado a Domínio — Padrões & Práticas
Quando Usar DDD
Use quando:
- Lógica de negócio complexa que vai além de CRUD
- Múltiplos especialistas de domínio com vocabulários diferentes
- O modelo de domínio é a vantagem competitiva
- Aplicações de nível empresarial
Não use quando:
- Aplicações CRUD simples
- Hackathon/MVP (exagero)
- Nenhum especialista de domínio disponível
Design Estratégico
Contextos Delimitados
O conceito mais importante do DDD. Um Contexto Delimitado é um limite dentro do qual um modelo específico é definido e aplicável.
Exemplo — E-Commerce:
[Contexto Pedido] [Contexto Pagamento] [Contexto Envio]
- Pedido - Pagamento - Remessa
- ItemPedido - Transação - CódigoRastreio
- Cliente (ref) - Reembolso - Endereço
- Endereço (valor) - Fatura - Transportadora
Cliente significa coisas diferentes em cada contexto:
- Contexto Pedido: nome, e-mail, preferência de envio
- Contexto Pagamento: informações de faturamento, métodos de pagamento
- Contexto Suporte: histórico de tickets, pontuação de satisfação
Mapa de Contexto
[Pedido] ←→ [Pagamento] # Parceria
[Pedido] → [Envio] # Cliente-Fornecedor
[Pedido] → [CRM Legado] # Camada Anticorrupção
Design Tático
Entidades
Objetos com identidade. Duas entidades com os mesmos atributos, mas IDs diferentes, são diferentes.
class Usuario {
readonly id: UsuarioId;
nome: string;
email: Email; // Objeto de Valor
}
Objetos de Valor
Objetos definidos por seus atributos, não pela identidade. Imutáveis.
class Email {
constructor(readonly value: string) {
if (!isValidEmail(value)) throw new InvalidEmailError(value);
}
equals(other: Email): boolean {
return this.value === other.value;
}
}
Agregados
Um cluster de entidades e objetos de valor com uma única raiz (Raiz do Agregado). Todo acesso passa pela raiz.
class Pedido { // Raiz do Agregado
private items: ItemPedido[] = [];
addItem(produto: ReferenciaProduto, quantidade: number): void {
// Lógica de negócio AQUI, não em um serviço
if (quantidade <= 0) throw new InvalidQuantityError();
this.items.push(new ItemPedido(produto, quantidade));
}
get total(): Dinheiro {
return this.items.reduce((soma, item) => soma.add(item.subtotal), Dinheiro.zero());
}
}
Regras do Agregado:
- Referencie outros agregados apenas por ID
- Um agregado por transação
- Consistência eventual entre agregados
Eventos de Domínio
Algo que aconteceu no domínio e que os especialistas de domínio se importam.
class PedidoRealizado implements EventoDominio {
constructor(
readonly pedidoId: PedidoId,
readonly clienteId: ClienteId,
readonly total: Dinheiro,
readonly ocorridoEm: Date
) {}
}
Serviços de Domínio
Lógica de negócio que não pertence naturalmente a uma entidade ou objeto de valor.
class ServicoPrecificacao {
calcularPreco(pedido: Pedido, cliente: Cliente, promocoes: Promocao[]): Dinheiro {
// Lógica de precificação complexa envolvendo múltiplos agregados
}
}
Repositórios
Abstração sobre acesso a dados. Um repositório por raiz de agregado.
interface RepositorioPedido {
buscarPorId(id: PedidoId): Promise<Pedido | null>;
salvar(pedido: Pedido): Promise<void>;
deletar(id: PedidoId): Promise<void>;
}
Estrutura de Diretórios (DDD)
src/
├── modulos/
│ └── pedidos/ # Contexto Delimitado
│ ├── dominio/
│ │ ├── entidades/
│ │ │ └── pedido.ts # Raiz do Agregado
│ │ ├── objetos-de-valor/
│ │ │ └── dinheiro.ts
│ │ ├── eventos/
│ │ │ └── pedido-realizado.ts
│ │ ├── servicos/
│ │ │ └── precificacao.ts
│ │ └── repositorios/
│ │ └── pedido.repository.ts # Interface
│ ├── aplicacao/
│ │ ├── comandos/
│ │ │ └── realizar-pedido.ts
│ │ ├── consultas/
│ │ │ └── obter-pedido.ts
│ │ └── manipuladores/
│ │ └── realizar-pedido.handler.ts
│ └── infraestrutura/
│ ├── persistencia/
│ │ └── pedido.repository.impl.ts # Implementação
│ └── api/
│ └── pedidos.controller.ts
A Regra de Dependência da Arquitetura Limpa
Em DDD, as dependências DEVEM apontar estritamente para dentro:
[ Frameworks & Drivers (Web, BD, UI) ]
└──▶ [ Adaptadores de Interface (Controllers, Gateways) ]
└──▶ [ Aplicação (Casos de Uso, Handlers CQRS) ]
└──▶ [ Domínio (Entidades, Objetos de Valor) ]
- A camada de Domínio tem ZERO dependências de ORMs (Prisma, TypeORM), frameworks HTTP (Express, NestJS) ou SDKs externos.
- Repositórios são definidos como interfaces na camada de domínio/aplicação e implementados na camada de infraestrutura.
Eventos de Domínio vs Eventos de Integração
- Eventos de Domínio: Representam mudanças de estado dentro de um único Contexto Delimitado.
- Acionados diretamente dentro da Raiz do Agregado (
pedido.adicionarItem(...)acionaItemAdicionado). - Despachados em processo antes do commit da transação.
- Acionados diretamente dentro da Raiz do Agregado (
- Eventos de Integração: Publicados através dos limites dos Contextos Delimitados para comunicar com outros serviços.
- Despachados via padrão Transactional Outbox para brokers de mensagens.
- Devem usar esquemas compatíveis com versões anteriores.
Camada Anticorrupção (ACL)
Ao consumir dados de um contexto delimitado externo ou API de fornecedor terceirizado (ex.: Stripe, Salesforce):
- NUNCA importe modelos de domínio externos diretamente para o seu domínio.
- Crie um Tradutor/Adaptador ACL na camada de infraestrutura para converter DTOs externos em seus próprios Objetos de Valor e Entidades.
Antipadrões
- [FALHA] Modelo de domínio anêmico — entidades com apenas getters/setters, toda a lógica em serviços
- [FALHA] Agregado grande — agregados devem ser pequenos, focados em invariantes
- [FALHA] Transações entre agregados — use consistência eventual
- [FALHA] DDD em todo lugar — use DDD apenas onde a complexidade justifica
- [FALHA] Entidades ORM vazando para o Domínio — entidades de domínio não devem depender de
@Entity()ou decoradores ORM
Exemplos de Código
Consulte EXAMPLES.md para exemplos de código detalhados.
Lista de Verificação de Validação
O que verificar durante a fase de revisão antes de concluir a tarefa.
Erros Comuns
Antipadrões e coisas a evitar explicitamente. Consulte TROUBLESHOOTING.md.
Notas de Integração
Como esta skill interage com outras skills.