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 de transação auditada". Este detalhe importa no código.
# 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()
Bounded Contexts
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.
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.
# 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
Padrões Estratégicos e Táticos
Entidades e Value Objects
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.
# 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)
Agregados
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.
# 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"
Repositórios e Services
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.
# 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)
Implementação Prática
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.
Uma estrutura de pastas recomendada separa domínio de infraestrutura:
projeto/
├── dominio/
│ ├── entidades/
│ ├── value_objects/
│ └── services/
├── aplicacao/
│ └── casos_uso/
├── infraestrutura/
│ ├── repositorios/
│ └── banco_dados/
└── apresentacao/
└── api/
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.
Conclusão
Domain-Driven Design não é apenas padrões de código—é uma mudança de mindset. Primeiro: linguagem e comunicação, alinhando desenvolvedores com negócio através da Ubiquitous Language. Segundo: modelagem orientada ao domínio, onde Bounded Contexts, Agregados e Entidades refletem a realidade do negócio, não apenas dados de banco. Terceiro: manutenibilidade de longo prazo, pois código bem estruturado em torno do domínio é mais resiliente a mudanças.
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.