ATLAS / MODELAGEM DE DOMÍNIO / FABRÍCIO CASTRO
O domínio começa
na linguagem do negócio.
Domain-Driven Design aproxima especialistas e software para modelar decisões complexas. Contextos delimitados e linguagem ubíqua orientam as fronteiras; entidades, objetos de valor e agregados sustentam invariantes onde isso se justifica. O objetivo não é multiplicar classes com nomes de padrões.
01 / DESCOBERTA ANTES DO FRAMEWORK
A mesma palavra pode significar duas coisas diferentes.
Imagine um negócio fictício que solicita compras, decide alçadas e integra registros em um ERP externo. “Pedido” pode ser uma solicitação interna no contexto de Compras, um documento de aquisição no ERP ou um compromisso financeiro em Contas a Pagar. Reutilizar a mesma entidade e os mesmos estados em todos esses contextos cria acoplamento semântico.
Quem toma cada decisão e em que momento?
Mapeie evento de negócio, aprovadores, solicitantes, limites financeiros, exceções, vocabulário e evidências. Escute os especialistas: não confunda nomes de tabelas do sistema legado com conceitos universalmente válidos.
Critério de aceite: cada regra deve ter um exemplo positivo, um contraexemplo e um responsável de negócio.
Bounded context não é sinônimo de microsserviço.
Uma fronteira conceitual e de linguagem pode existir dentro de um monólito modular. Uma organização que ainda está aprendendo o domínio pode preferir limites internos claros antes de assumir as falhas e os custos de operação de serviços separados.
Decisão revisável: o tamanho do deployment não define a qualidade da modelagem.
02 / ESTRATÉGIA DE DOMÍNIO
Contextos delimitados protegem significado.
Divida conversas de negócio por responsabilidades: Compras gerencia a solicitação e suas transições; o ERP externo responde pelo registro sob seu próprio contrato; Financeiro confirma obrigações financeiras conforme regras próprias. O mapeamento abaixo é uma hipótese didática, não um diagnóstico de empresa real.
- 01Compras / linguagem local
“Solicitação”, “rascunho”, “enviada”, “solicitante” e “itens” pertencem à sua conversa de domínio. A equipe pode criar um glossário e registrar as decisões ambíguas como questões de descoberta.
- 02ERP / integração como fronteira
“Pedido” e seus códigos externos não devem contaminar todo o agregado de Compras. Uma Anti-Corruption Layer (ACL) traduz mensagens e identificadores conforme contrato real, sem prometer equivalência automática.
- 03Financeiro / semântica própria
Uma solicitação enviada não significa obrigação paga, lançada ou conciliada. Cada contexto tem regras e eventos correspondentes; as relações entre eles precisam de consistência e reconciliação explicitamente projetadas.
03 / DDD TÁTICO / FRONTEIRA DE CONSISTÊNCIA
Um agregado deve proteger invariantes verificáveis.
O exemplo usa PurchaseRequest como raiz do agregado. Ele possui identidade, organização, solicitante, linhas e total em centavos de BRL. A transição local é draft → submitted; aprovação, confirmação do ERP, pagamento e auditoria completa não fazem parte desta demonstração.
Solicitação não é somente uma linha de tabela.
O identificador permite reconhecer a mesma solicitação enquanto estado e itens mudam. A raiz controla o ciclo e impede que uma parte externa altere o estado de modo arbitrário.
Centavos inteiros evitam algumas ambiguidades.
Preço e total são expressos em inteiros seguros, com validação de quantidade e overflow. Não é solução geral de câmbio, impostos ou cálculo contábil: esses problemas requerem regras, escala e tipos próprios.
Itens precisam ser coerentes no mesmo pedido.
A raiz proíbe lista vazia, SKU duplicado, quantidade não positiva, preço negativo e total adulterado. Mudanças de estado não atualizam o estoque ou o financeiro dentro da mesma transação por serem outro contexto.
“PurchaseSubmitted” descreve o que ocorreu.
O evento traz identidade da solicitação, organização, versão e total, sem copiar toda a entidade. É produzido pela transição pura; durabilidade, publicação e idempotência dependem do caso de uso e da infraestrutura.
04 / CÓDIGO ORIGINAL / TYPESCRIPT
Uma regra explícita é melhor que um getter sofisticado.
As funções createPurchaseDraft, calculatePurchaseTotal e submitPurchaseRequest implementam invariantes sem importar Next.js, NestJS, Prisma ou SQL. O input para criação é validado na fronteira do modelo e a transição devolve um novo snapshot e um evento de domínio.
const draft = createPurchaseDraft({
id: "req-001", tenantId: "empresa-A", requesterId: "pessoa-1",
lines: [{ sku: "TECIDO-01", quantity: 2, unitPriceCents: 1250 }],
});
const result = submitPurchaseRequest(draft, {
userId: "pessoa-1", tenantId: "empresa-A",
});
// result.aggregate.status === "submitted"
// result.events[0].type === "PurchaseSubmitted"
// Nenhuma dessas funções salva no PostgreSQL.Fonte integral: src/lib/atlas/examples/procurement-domain.ts. Este trecho ilustra a API, não executa a função no navegador e não demonstra persistência.
INVARIANTES / TESTE DE DOMÍNIO
A entrada inválida não pode produzir estado válido.
Dados vindos de UI, API ou importação podem estar incorretos. O agregado deve rejeitar valores inconsistentes antes de qualquer intenção de persistência; uma validação de formulário não substitui regras do servidor.
IDENTIDADEIDs não vazios; solicitante e organização coerentes.
ITENSAo menos um SKU, sem duplicação, quantidade positiva e inteira.
MONETÁRIOCentavos inteiros, preços não negativos, total sem overflow.
TRANSIÇÃOSomente solicitante do mesmo tenant envia; apenas draft → submitted.
VERSIONAMENTOTransição calcula próxima versão, mas banco deve validar escrita condicional.
05 / EVENTO NÃO É ENTREGA GARANTIDA
Fato de domínio, transação local e rede não são a mesma coisa.
O método submitPurchaseRequest retorna PurchaseSubmitted na memória. A solicitação fica submitted no contexto de Compras; o estado pending do exemplo Strategy/State pertence a um fluxo de Aprovação distinto. Uma integração real precisaria mapear explicitamente esses estados, sem tratar os dois agregados como um só. Para um caso real, a camada de aplicação precisaria persistir agregado e mensagem de outbox de forma atômica, tratar versões concorrentes e publicar com retry/idempotência. Um DomainEvent em array não constitui infraestrutura de mensageria.
- 01Produzir o evento junto da mudança de domínio
O domínio gera a descrição do fato, sem mencionar Kafka, RabbitMQ, Socket.IO, SQL ou URL de webhooks. Assim, o vocabulário interno não depende do fornecedor de transporte.
- 02Persistir com atomicidade no limite correto
A porta de repositório especifica escrita por versão e armazenamento conjunto de evento. Somente um adaptador com transação real pode provar essa garantia; o fake não substitui PostgreSQL.
- 03Traduzir para outros contextos
Uma ACL converte conceitos sem expor a estrutura privada do agregado. O receptor remoto deve identificar duplicidade, falhas permanentes e casos de reconciliação.
- 04Corrigir o modelo quando a linguagem mudar
Se o negócio aprovar envios por delegados, permitir edição pós-envio ou separar alçadas, reescreva as regras e seus testes de domínio. Não esconda novos requisitos em adapters.
06 / TDD / PROVAS E LIMITES
Os testes precisam quebrar quando um invariante cai.
A suíte Jest dedicada contém casos para criação, total, preço/quantidade inválidos, duplicação de SKU, overflow monetário, rejeição de outros tenants, identidade incorreta, alteração de total e transição ilegal. A presença desses testes no repositório não significa que foram executados no SHA atual.
Criar e enviar uma solicitação coerente.
Devolver novo estado com versão incrementada e evento contendo total correto, sem mutar o snapshot anterior. O caso de uso também precisa verificar a escrita na porta de repositório.
Negar dados e ações sem autorização.
Bloquear tenant diferente, solicitante diferente, linhas vazias, SKUs repetidos, quantidade inválida e tentativa de enviar pedido já enviado.
Jest de funções puras não prova transação.
Concorrência real, outbox, segurança de autenticação, logs e migração de banco exigem testes adicionais com infraestrutura isolada e evidência reproduzível.
07 / CRITÉRIO E CONTRAEXEMPLO
DDD é um método de enfrentar complexidade. Não um ritual obrigatório.
Uma página institucional estática ou um CRUD simples com regras limitadas talvez não justifique dezenas de entidades, fábricas e repositórios. DDD estratégico continua útil para compreender vocabulário e responsabilidades; seu DDD tático deve ser proporcional à complexidade real, ao número de regras e à evolução esperada.
Entidade sem invariantes, serviço que sabe tudo.
Classes que só espelham tabelas e expõem setters públicos podem espalhar regras por controllers e serviços. Ao detectar essa dispersão, concentre as decisões próximas à fronteira do agregado.
Aplicar toda a taxonomia antes de uma necessidade.
Uma coleção de interfaces 1:1 e eventos para cada setter não garante manutenção mais fácil. Comece com regras verificáveis e use as abstrações necessárias para isolar mudanças observáveis.
PRÓXIMO CAPÍTULO / ARQUITETURA
O domínio é independente. Como a aplicação conversa com o mundo?
Clean Architecture e Ports & Adapters mostram a direção das dependências, o uso de portas e o custo de acoplar um caso de uso ao ORM ou HTTP.
08 / FONTES E REVISÃO EDITORIAL
O vocabulário deve ser confrontado com fontes e casos.
O exemplo e o diagrama são autorais e fictícios. A referência clássica é Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software (2003). A documentação moderna ajuda a comparar bounded contexts e agregados, mas padrões não provam que uma determinada empresa precise de microsserviços.
Microsoft Learn — desenho orientado a domínio.
Explicações sobre linguagem compartilhada, entidades, objetos de valor, agregados e limites de contexto. A proposta de serviços distribuídos é um cenário, não pré-condição de DDD.
Comparar com os limites de Arquitetura.
O capítulo já existente explica ADR, monólito modular, C4 e falhas de integração; a modelagem presente aprofunda apenas os conceitos próprios do domínio.
Status: conteúdo e código implementados em branch. Testes em runtime, QA visual, TypeScript, build e publicação exigem validação posterior. Não há projeto de cliente, ERP integrado ou PostgreSQL implementado neste estudo.