Ferramentas & Produtividade

Guia Completo de Domain-Driven Design

8 min de leitura

Guia Completo de Domain-Driven Design

O que é Domain-Driven Design? Domain-Driven Design (DDD) é uma abordagem para desenvolvimento de software que coloca o domínio do negócio no centro da arquitetura. Proposto por Eric Evans em 2003, DDD fornece padrões e linguagem comum (Ubiquitous Language) entre desenvolvedores e especialistas de negócio, reduzindo gaps de comunicação e criando sistemas mais alinhados com as necessidades reais. A essência de DDD não é apenas técnica—é cultural. Você não está apenas codificando, está modelando a realidade do negócio. Isso significa que sua estrutura de código reflete processos e conceitos do domínio, tornando o sistema mais intuitivo, mantível e escalável a longo prazo. Conceitos Fundamentais Ubiquitous Language A Linguagem Ubíqua é o vocabulário compartilhado entre desenvolvedores e stakeholders. Cada termo deve ter um significado preciso, usado nos testes, documentação, código e conversas. Isso elimina ambiguidades e alinha todos os envolvidos. Exemplo: Em um sistema bancário, não dizemos "transferência de dinheiro"—dizemos "débito da conta origem e crédito da conta destino com geração

<h2>O que é Domain-Driven Design?</h2>

<p>Domain-Driven Design (DDD) é uma abordagem para desenvolvimento de software que coloca o domínio do negócio no centro da arquitetura. Proposto por Eric Evans em 2003, DDD fornece padrões e linguagem comum (Ubiquitous Language) entre desenvolvedores e especialistas de negócio, reduzindo gaps de comunicação e criando sistemas mais alinhados com as necessidades reais.</p>

<p>A essência de DDD não é apenas técnica—é cultural. Você não está apenas codificando, está modelando a realidade do negócio. Isso significa que sua estrutura de código reflete processos e conceitos do domínio, tornando o sistema mais intuitivo, mantível e escalável a longo prazo.</p>

<h2>Conceitos Fundamentais</h2>

<h3>Ubiquitous Language</h3>

<p>A Linguagem Ubíqua é o vocabulário compartilhado entre desenvolvedores e stakeholders. Cada termo deve ter um significado preciso, usado nos testes, documentação, código e conversas. Isso elimina ambiguidades e alinha todos os envolvidos.</p>

<p>Exemplo: Em um sistema bancário, não dizemos &quot;transferência de dinheiro&quot;—dizemos &quot;débito da conta origem e crédito da conta destino com geração de transação auditada&quot;. Este detalhe importa no código.</p>

<pre><code class="language-python"># Ubiquitous Language refletido no código

class Transacao:

def __init__(self, conta_origem: Conta, conta_destino: Conta, valor: Decimal):

self.conta_origem = conta_origem

self.conta_destino = conta_destino

self.valor = valor

self.timestamp = datetime.now()

def executar(self):

self.conta_origem.debitar(self.valor)

self.conta_destino.creditar(self.valor)

self._registrar_auditoria()</code></pre>

<h3>Bounded Contexts</h3>

<p>Um Bounded Context é um espaço explícito onde um modelo de domínio se aplica. Grandes aplicações precisam ser divididas em contextos menores com modelos específicos. Cada contexto tem sua própria linguagem ubíqua e pode usar diferentes tecnologias.</p>

<p>Exemplo: Em um e-commerce, o contexto de &quot;Catálogo&quot; modela produtos diferentemente do contexto de &quot;Pedidos&quot;. No Catálogo, produto tem foto, descrição, preço sugerido. No Pedido, produto é apenas código, quantidade e preço vendido.</p>

<pre><code class="language-python"># Bounded Context: Catálogo

class ProdutoCatalogo:

def __init__(self, id: str, nome: str, descricao: str, preco_sugerido: Decimal):

self.id = id

self.nome = nome

self.descricao = descricao

self.preco_sugerido = preco_sugerido

Bounded Context: Pedidos

class ItemPedido:

def __init__(self, codigo_produto: str, quantidade: int, preco_vendido: Decimal):

self.codigo_produto = codigo_produto

self.quantidade = quantidade

self.preco_vendido = preco_vendido

def subtotal(self) -&gt; Decimal:

return self.preco_vendido * self.quantidade</code></pre>

<h2>Padrões Estratégicos e Táticos</h2>

<h3>Entidades e Value Objects</h3>

<p>Entidades possuem identidade única e ciclo de vida (um Cliente em um banco). Value Objects não possuem identidade—importa apenas seu valor (um Endereço, uma Moeda). Value Objects são imutáveis e comparáveis por valor.</p>

<pre><code class="language-python"># Value Object - imutável

class Moeda:

def __init__(self, valor: Decimal, moeda: str):

self._valor = valor

self._moeda = moeda

def __eq__(self, outro):

return self._valor == outro._valor and self._moeda == outro._moeda

def adicionar(self, outra: &#039;Moeda&#039;) -&gt; &#039;Moeda&#039;:

if self._moeda != outra._moeda:

raise ValueError(&quot;Moedas diferentes&quot;)

return Moeda(self._valor + outra._valor, self._moeda)

Entidade - com identidade única

class Conta:

def __init__(self, numero: str, titular: str):

self.numero = numero # Identidade

self.titular = titular

self.saldo = Moeda(Decimal(0), &quot;BRL&quot;)

def depositar(self, moeda: Moeda):

self.saldo = self.saldo.adicionar(moeda)</code></pre>

<h3>Agregados</h3>

<p>Um Agregado é um cluster de entidades e value objects tratados como unidade atômica. Tem uma Raiz Agregada (Aggregate Root) responsável por manter consistência. Só você referencia a raiz; entidades internas são privadas.</p>

<pre><code class="language-python"># Agregado: Pedido

class Pedido: # Raiz Agregada

def __init__(self, id: str, cliente_id: str):

self.id = id

self.cliente_id = cliente_id

self._itens = [] # Privado

self.status = &quot;novo&quot;

def adicionar_item(self, codigo_produto: str, quantidade: int, preco: Decimal):

if self.status != &quot;novo&quot;:

raise ValueError(&quot;Pedido não pode ser modificado&quot;)

item = ItemPedido(codigo_produto, quantidade, preco)

self._itens.append(item)

def total(self) -&gt; Decimal:

return sum(item.subtotal() for item in self._itens)

def confirmar(self):

if not self._itens:

raise ValueError(&quot;Pedido vazio&quot;)

self.status = &quot;confirmado&quot;</code></pre>

<h3>Repositórios e Services</h3>

<p>Repositórios abstraem a persistência, permitindo recuperar agregados por sua identidade. Domain Services encapsulam lógica que não pertence a uma entidade específica—algo que atravessa múltiplos agregados.</p>

<pre><code class="language-python"># Repositório

class RepositorioPedido:

def __init__(self, database):

self.db = database

def salvar(self, pedido: Pedido):

self.db.save(&quot;pedidos&quot;, pedido.id, pedido.__dict__)

def obter_por_id(self, id: str) -&gt; Pedido:

dados = self.db.get(&quot;pedidos&quot;, id)

if not dados:

raise ValueError(f&quot;Pedido {id} não encontrado&quot;)

return self._reconstruir(dados)

Domain Service

class ServicoTransferencia:

def __init__(self, repositorio_conta: RepositorioConta):

self.repo = repositorio_conta

def transferir(self, numero_origem: str, numero_destino: str, valor: Decimal):

conta_origem = self.repo.obter_por_numero(numero_origem)

conta_destino = self.repo.obter_por_numero(numero_destino)

conta_origem.debitar(valor)

conta_destino.creditar(valor)

self.repo.salvar(conta_origem)

self.repo.salvar(conta_destino)</code></pre>

<h2>Implementação Prática</h2>

<p>Ao implementar DDD, comece identificando seu Bounded Context principal. Mapeie a Ubiquitous Language com especialistas de negócio. Modele agregados que respeitem invariantes de negócio (regras que nunca podem ser quebradas). Use Repositórios para persistência, nunca deixe infraestrutura vazar para o domínio.</p>

<p>Uma estrutura de pastas recomendada separa domínio de infraestrutura:</p>

<pre><code>projeto/

├── dominio/

│ ├── entidades/

│ ├── value_objects/

│ └── services/

├── aplicacao/

│ └── casos_uso/

├── infraestrutura/

│ ├── repositorios/

│ └── banco_dados/

└── apresentacao/

└── api/</code></pre>

<p>O domínio deve ser independente, testável sem mocks pesados. Se precisar mockar banco de dados para testar lógica de domínio, sua modelagem está vazando infraestrutura.</p>

<h2>Conclusão</h2>

<p>Domain-Driven Design não é apenas padrões de código—é uma mudança de mindset. Primeiro: <strong>linguagem e comunicação</strong>, alinhando desenvolvedores com negócio através da Ubiquitous Language. Segundo: <strong>modelagem orientada ao domínio</strong>, onde Bounded Contexts, Agregados e Entidades refletem a realidade do negócio, não apenas dados de banco. Terceiro: <strong>manutenibilidade de longo prazo</strong>, pois código bem estruturado em torno do domínio é mais resiliente a mudanças.</p>

<p>Comece pequeno, com um Bounded Context bem definido. Aprenda a modelar agregados respeitando invariantes. Depois escale. DDD é poderoso, mas exige disciplina e conversa constante com o negócio.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.domainlanguage.com/ddd/" target="_blank" rel="noopener noreferrer">Domain-Driven Design - Eric Evans (Livro)</a></li>

<li><a href="https://vaughnvernon.com/" target="_blank" rel="noopener noreferrer">Implementing Domain-Driven Design - Vaughn Vernon</a></li>

<li><a href="https://www.domaindriven.org/" target="_blank" rel="noopener noreferrer">DDD Community - Documentação e Padrões</a></li>

<li><a href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/" target="_blank" rel="noopener noreferrer">Microsoft - DDD em Arquitetura</a></li>

<li><a href="https://www.thoughtworks.com/insights/articles/domain-driven-design" target="_blank" rel="noopener noreferrer">ThoughtWorks - Domain-Driven Design</a></li>

</ul>

Comentários

Mais em Ferramentas & Produtividade

Logs que realmente respondem perguntas: do clique no React até o banco de dados
Logs que realmente respondem perguntas: do clique no React até o banco de dados

Bug às 23h, mil linhas de log sem contexto. Veja como estruturar logs rastreá...

DevOps: Do Básico ao Avançado
DevOps: Do Básico ao Avançado

O que é DevOps e Por Que Importa DevOps é uma cultura que unifica desenvolvim...

Escalabilidade Horizontal na Prática
Escalabilidade Horizontal na Prática

O que é Escalabilidade Horizontal Escalabilidade horizontal, também conhecida...