Guia Completo de Domain-Driven Design Já leu

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

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.

Referências


Artigos relacionados