Software · Arquitetura de Software
Domain-Driven Design na Prática: Modelagem Estratégica, Tática e Linguagem Ubíqua
Como aplicar o Domain-Driven Design para alinhar código e negócio através de Bounded Contexts estratégicos e padrões táticos como Agregados e Objetos de Valor.
Quando sistemas corporativos crescem guiados exclusivamente por tabelas de banco de dados e modelos anêmicos de dados, o código perde o vínculo com as reais necessidades da organização. Regras críticas de validação espalham-se de maneira desordenada entre controladores web, procedimentos armazenados (stored procedures) e utilitários auxiliares, gerando modelos frágeis em que qualquer alteração resulta em comportamentos imprevistos.
Introduzido por Eric Evans em 2003, o Domain-Driven Design (DDD) propõe uma mudança de perspectiva: o coração do software deve refletir diretamente o problema de negócio que se busca solucionar. Em vez de subordinar a arquitetura a escolhas de frameworks ou bancos de dados, o DDD estrutura o desenvolvimento em torno de uma linguagem compartilhada entre desenvolvedores e especialistas do domínio, organizada em dois pilares complementares: o Design Estratégico e o Design Tático.
O Pilar Estratégico: Bounded Contexts e Linguagem Ubíqua
Em empresas de médio e grande porte, a tentativa de construir um modelo de dados único e unificado para toda a corporação sempre fracassa. Um termo aparentemente elementar como "Cliente" ou "Produto" assume significados profundamente conflitantes dependendo do departamento:
- Na área de Vendas, um Produto é definido por catálogo de exibição, preço promocional, campanhas de desconto e fotos de marketing.
- Na área de Logística, o mesmo Produto é caracterizado por dimensões físicas (altura, largura, profundidade), peso líquido, restrições de empilhamento e código de barras no armazém.
- No departamento Fiscal, o Produto resume-se a regras de tributação (NCM, ICMS) e regras de substituição tributária.
graph TD
Problem["<b>ESPAÇO DO PROBLEMA: E-COMMERCE</b>"]
Problem --> BC_Vendas["<b>Bounded Context: Vendas</b><br/>Modelo: <i>Produto</i><br/>(Preço, Catálogo, Descontos, Fotos)"]
Problem --> BC_Log["<b>Bounded Context: Logística</b><br/>Modelo: <i>Volume / Pacote</i><br/>(Peso, Cubagem, Rastreamento)"]
Problem --> BC_Fisc["<b>Bounded Context: Fiscal</b><br/>Modelo: <i>Item Tributário</i><br/>(NCM, Alíquotas, CFOP)"]
BC_Vendas --> UL["<b>Linguagem Ubíqua Específica por Contexto</b><br/>(Contratos explícitos entre fronteiras)"]
BC_Log --> UL
BC_Fisc --> UL
classDef prob fill:#0f172a,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
classDef bc fill:#132338,stroke:#64748b,stroke-width:1px,color:#f8fafc;
classDef ul fill:#0f2724,stroke:#34d399,stroke-width:2px,color:#f8fafc;
class Problem prob;
class BC_Vendas,BC_Log,BC_Fisc bc;
class UL ul;
O DDD resolve essa sobreposição através do conceito de Bounded Context (Contexto Delimitado). Um Bounded Context define uma fronteira linguística e arquitetural explícita dentro da qual um modelo específico possui validade absoluta e incontestável.
Dentro dessa fronteira vigora a Linguagem Ubíqua (Ubiquitous Language): um vocabulário rigoroso compartilhado entre especialistas de negócio e equipe técnica. Se os analistas de frete utilizam a expressão "Cubagem", essa palavra deve figurar exatamente dessa forma nas classes, métodos, testes automatizados e conversas do time, eliminando ambiguidades e traduções mentais desnecessárias.
O Pilar Tático: Construção do Modelo Rico
Enquanto o design estratégico estabelece as fronteiras do sistema, o design tático fornece os blocos de construção para expressar as regras de negócio no código:
1. Entidades (Entities)
Objetos que possuem identidade conceitual contínua ao longo do tempo. Dois clientes com o mesmo nome e mesmo endereço continuam sendo indivíduos distintos se possuírem identificadores únicos diferentes. O ciclo de vida de uma entidade inclui mutações, mas sua essência identificadora permanece estável.
2. Objetos de Valor (Value Objects)
Objetos que descrevem características ou medidas do domínio, mas não possuem identidade própria. São estritamente imutáveis: uma vez instanciados, não podem sofrer alteração interna; qualquer modificação produz uma nova instância. Exemplos clássicos incluem Dinheiro(valor, moeda), Endereco(rua, cep) ou Email(endereco). A igualdade de dois Objetos de Valor é baseada na equivalência de seus atributos estruturais, e não em uma chave primária.
// Objeto de Valor imutável com validações embutidas no construtor
public readonly record struct Dinheiro
{
public decimal Quantia { get; }
public string Moeda { get; }
public Dinheiro(decimal quantia, string moeda)
{
if (quantia < 0)
throw new ArgumentException("Quantia monetária não pode ser negativa.");
if (string.IsNullOrWhiteSpace(moeda))
throw new ArgumentException("Moeda deve ser informada.");
Quantia = quantia;
Moeda = moeda.ToUpperInvariant();
}
public Dinheiro Somar(Dinheiro outro)
{
if (Moeda != outro.Moeda)
throw new InvalidOperationException("Não é possível somar moedas diferentes.");
return new Dinheiro(Quantia + outro.Quantia, Moeda);
}
}
3. Agregados e Raiz do Agregado (Aggregates & Aggregate Root)
Um Agregado é um agrupamento coeso de Entidades e Objetos de Valor tratado como uma única unidade transacional para gravação. Para garantir a integridade dos dados, uma das entidades é eleita como a Raiz do Agregado (Aggregate Root).
A regra de ouro dos agregados estabelece que nenhum elemento externo pode manter referências diretas para os componentes internos do grupo: toda comunicação, alteração ou consulta deve passar obrigatoriamente pelos métodos públicos da Raiz. Se um Pedido contém uma lista de ItensDePedido, nenhum controlador externo pode alterar a quantidade de um item diretamente; é o Pedido que invoca o método de recálculo, garantindo que invariantes como limite máximo de itens e descontos globais sejam respeitados simultaneamente.
4. Eventos de Domínio (Domain Events)
Representam fatos marcantes ocorridos no passado do domínio que possuem relevância para os especialistas de negócio. São sempre batizados com verbos no particípio ou passado (PedidoAprovado, PagamentoEstornado, LimiteDeCreditoAtingido).
Os agregados registram eventos de domínio ao final de suas operações internas. Esses eventos podem ser consumidos sincronamente por manipuladores dentro do mesmo processo ou despachados como eventos de integração para corretores assíncronos, propagando reações secundárias sem acoplar os contextos.
A Relação com Microsserviços e Monólitos Modulares
O design estratégico do DDD constitui a fundação teórica mais consistente para definir o escopo de serviços modernos:
- Microsserviços bem modelados: Devem espelhar a fronteira de um Bounded Context. Quando microsserviços são criados por entidades puras (como "Serviço de Usuário", "Serviço de Item"), cria-se um acoplamento excessivo de rede e consultas distribuídas distribuídas ineficientes.
- Monólitos Modulares saudáveis: Em vez de dividir a aplicação em múltiplos serviços físicos de imediato, organiza-se uma base de código única com namespaces e pacotes estritamente isolados por Bounded Contexts, permitindo que a equipe evolua as regras de negócio com a simplicidade operacional de um único executável.