<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 "transferência de dinheiro"—dizemos "débito da conta origem e crédito da conta destino com geração de transação auditada". 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 "Catálogo" modela produtos diferentemente do contexto de "Pedidos". 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) -> 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: 'Moeda') -> 'Moeda':
if self._moeda != outra._moeda:
raise ValueError("Moedas diferentes")
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), "BRL")
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 = "novo"
def adicionar_item(self, codigo_produto: str, quantidade: int, preco: Decimal):
if self.status != "novo":
raise ValueError("Pedido não pode ser modificado")
item = ItemPedido(codigo_produto, quantidade, preco)
self._itens.append(item)
def total(self) -> Decimal:
return sum(item.subtotal() for item in self._itens)
def confirmar(self):
if not self._itens:
raise ValueError("Pedido vazio")
self.status = "confirmado"</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("pedidos", pedido.id, pedido.__dict__)
def obter_por_id(self, id: str) -> Pedido:
dados = self.db.get("pedidos", id)
if not dados:
raise ValueError(f"Pedido {id} não encontrado")
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>