DDD: Introdução aos conceitos fundamentais (Parte 1)
Este é o primeiro artigo de uma série sobre Domain-Driven Design (DDD). Se você já ouviu falar sobre DDD mas nunca entendeu direito do que se trata, ou se está começando a trabalhar com sistemas complexos, este artigo é para você.
O que é Domain-Driven Design?
Domain-Driven Design não é uma tecnologia, não é um framework, não é uma metodologia ágil. DDD é uma abordagem para desenvolvimento de software que coloca o domínio do negócio no centro do design. Foi criado por Eric Evans e publicado em 2003 no livro "Domain-Driven Design: Tackling Complexity in the Heart of Software".
Quando Evans criou o DDD, ele estava tentando resolver um problema que todo desenvolvedor já enfrentou: como lidar com a complexidade de sistemas grandes? A resposta dele foi simples, mas revolucionária: coloque o domínio no centro de tudo.
O domínio é a área de conhecimento em que sua aplicação atua. Se você está desenvolvendo um sistema bancário, o domínio envolve conceitos como contas, transferências, empréstimos, juros. Se é um e-commerce, envolve produtos, pedidos, pagamentos, estoque. O DDD propõe que estes conceitos de negócio devem guiar completamente a estrutura do código.
A Filosofia por Trás do DDD
O DDD surge de uma percepção fundamental: a maior parte da complexidade em software empresarial não está na tecnologia, mas sim na compreensão e modelagem correta das regras de negócio. Muitas vezes, desenvolvedores passam mais tempo lutando contra código confuso do que resolvendo problemas reais do domínio.
Eric Evans observou que os melhores sistemas são aqueles onde desenvolvedores e especialistas de domínio conseguem conversar de forma fluida sobre o problema que estão resolvendo. Quando essa comunicação falha, o software inevitavelmente falha também.
Os Três Pilares do DDD
DDD se baseia em três pilares fundamentais que trabalham em conjunto:
1. Linguagem Ubíqua (Ubiquitous Language)
A Linguagem Ubíqua é provavelmente o conceito mais importante e transformador do DDD. É uma linguagem comum entre desenvolvedores e especialistas de domínio. Parece simples, mas na prática é revolucionário.
Problema comum sem Linguagem Ubíqua:
- Desenvolvedor fala: "Vamos criar uma tabela
user_cart_items
com foreign key paraproduct_table
" - Analista de negócio fala: "O cliente quer adicionar itens ao carrinho de compras"
- Stakeholder fala: "Precisamos permitir que o usuário coloque produtos na cesta"
Solução com Linguagem Ubíqua:
- Todos falam: "O Cliente adiciona Produtos ao Carrinho e depois realiza o Checkout"
A linguagem ubíqua deve aparecer literalmente em todos os lugares: no código, na documentação, nas reuniões, nos testes, nos nomes de métodos e classes. Se o negócio chama de "Cliente", o código deve ter uma classe Cliente
, não User
ou Person
. Esta consistência elimina uma fonte gigantesca de mal-entendidos e bugs.
2. Design Estratégico
O Design Estratégico é onde definimos a arquitetura macro do sistema. Trata de como dividir um sistema complexo em partes menores e mais manejáveis, cada uma com sua responsabilidade bem definida.
O conceito central aqui é o Bounded Context (Contexto Delimitado). Um Bounded Context é uma fronteira conceitual dentro da qual um modelo de domínio é válido e consistente. Dentro desta fronteira, cada termo tem um significado específico e não ambíguo.
Vamos usar um exemplo prático: Em um sistema de e-commerce, você pode ter diferentes contextos:
- Contexto de Vendas: Onde "Cliente" significa uma pessoa que tem histórico de compras, cartão de crédito cadastrado e endereço de entrega
- Contexto de Suporte: Onde "Cliente" significa alguém que abriu um ticket, tem um problema específico e precisa de atendimento
- Contexto de Marketing: Onde "Cliente" pode significar um lead, prospect ou alguém que se inscreveu na newsletter
O mesmo termo ("Cliente") tem significados e atributos diferentes em cada contexto. Isso não é um problema - é natural! O DDD abraça essa realidade ao invés de tentar forçar um modelo único.
3. Design Tático
O Design Tático é onde implementamos o modelo de domínio no código. Aqui entram os "building blocks" ou blocos de construção do DDD. São padrões que nos ajudam a estruturar o código de forma que ele reflita fielmente o domínio.
Os principais building blocks são:
- Entities (Entidades): Objetos com identidade única
- Value Objects (Objetos de Valor): Objetos definidos pelos seus atributos
- Aggregates (Agregados): Grupos de objetos que mantêm consistência
- Domain Services (Serviços de Domínio): Operações que não pertencem a uma entidade específica
- Repositories (Repositórios): Abstrações para acesso a dados
Exemplo Prático: Sistema de Biblioteca
Para ilustrar como os conceitos se aplicam na prática, vamos modelar um sistema de biblioteca usando DDD. Este exemplo nos ajudará a entender como a teoria se traduz em código real.
Definindo a Linguagem Ubíqua
Primeiro, precisamos estabelecer os termos que nossa equipe (incluindo bibliotecários) usará:
- Livro: Item físico que pode ser emprestado, tem ISBN, título, autor
- Membro: Pessoa registrada que pode pegar livros emprestados
- Empréstimo: Ato de um membro retirar um livro por período determinado
- Devolução: Ato de retornar um livro emprestado
- Reserva: Solicitação para emprestar um livro que está indisponível
Implementando Entidades
Entidades são objetos que têm identidade única e ciclo de vida próprio. Mesmo que seus atributos mudem, a identidade permanece. Vamos ver como isso se traduz em código PHP:
<?php
class Cliente
{
private string $id;
private string $nome;
private string $email;
public function __construct(string $id, string $nome, string $email)
{
$this->id = $id;
$this->nome = $nome;
$this->email = $email;
}
// Mesmo que o nome mude, ainda é o mesmo cliente
public function mudarNome(string $novoNome): void
{
if (empty($novoNome)) {
throw new InvalidArgumentException('Nome não pode ser vazio');
}
$this->nome = $novoNome;
}
public function getId(): string
{
return $this->id;
}
public function getNome(): string
{
return $this->nome;
}
}
Note como a classe Cliente
reflete exatamente o termo usado no negócio. O método mudarNome()
demonstra que a identidade ($id
) permanece constante, mesmo quando outros atributos mudam. Esta é a essência de uma entidade.
Implementando Value Objects
Value Objects são objetos definidos pelos seus atributos, não por identidade. Dois emails com o mesmo valor são considerados iguais. Eles também são imutáveis - se você quer mudar um value object, você cria um novo:
<?php
class Email
{
private string $valor;
public function __construct(string $valor)
{
if (!$this->isValid($valor)) {
throw new InvalidArgumentException('Email inválido');
}
$this->valor = $valor;
}
private function isValid(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public function getValor(): string
{
return $this->valor;
}
public function equals(Email $outro): bool
{
return $this->valor === $outro->valor;
}
}
O Value Object Email
encapsula a lógica de validação e garante que nunca teremos um email inválido no sistema. A validação acontece no momento da criação, implementando o princípio "fail fast".
Implementando um Agregado
Agregados são grupos de entidades e value objects que devem ser tratados como uma unidade para manter a consistência. Vamos implementar o agregado Emprestimo
:
<?php
class Emprestimo
{
private string $id;
private Livro $livro;
private Membro $membro;
private DateTime $dataEmprestimo;
private ?DateTime $dataDevolucao;
private function __construct(
string $id,
Livro $livro,
Membro $membro,
DateTime $dataEmprestimo
) {
$this->id = $id;
$this->livro = $livro;
$this->membro = $membro;
$this->dataEmprestimo = $dataEmprestimo;
$this->dataDevolucao = null;
}
public static function criar(Livro $livro, Membro $membro): self
{
if (!$membro->podeEmprestar()) {
throw new DomainException('Membro já tem o máximo de empréstimos');
}
if (!$livro->estaDisponivel()) {
throw new DomainException('Livro não está disponível');
}
$livro->emprestar();
return new self(
uniqid(), // Em produção, use um gerador de ID adequado
$livro,
$membro,
new DateTime()
);
}
public function devolver(): void
{
if ($this->dataDevolucao !== null) {
throw new DomainException('Livro já foi devolvido');
}
$this->livro->devolver();
$this->dataDevolucao = new DateTime();
}
}
O método estático criar()
é um exemplo de como encapsular regras de negócio complexas. Ele verifica várias condições antes de permitir a criação do empréstimo, garantindo que o sistema nunca entre em um estado inconsistente.
Quando Usar DDD?
DDD não é uma solução universal. Como qualquer ferramenta, tem contextos onde brilha e outros onde pode ser overkill. Vamos analisar objetivamente quando aplicar DDD:
Cenários Ideais para DDD
Domínio Complexo: Sistemas bancários, seguradoras, ERPs, sistemas de saúde, e-commerce grandes. Estes domínios têm regras de negócio intrincadas, muitas exceções, e consequências significativas quando algo dá errado.
Lógica de Negócio que Muda Frequentemente: Se as regras do seu domínio mudam constantemente devido a regulamentações, necessidades de mercado, ou crescimento do negócio, DDD oferece a flexibilidade necessária.
Presença de Especialistas de Domínio: DDD funciona melhor quando você tem acesso a pessoas que realmente entendem o negócio. Médicos, contadores, analistas financeiros, gerentes de produto experientes.
Projetos de Longa Duração: O investimento inicial em modelagem DDD compensa em projetos que durarão anos, onde a manutenibilidade é crucial.
Quando NÃO Usar DDD
Projetos Simples (CRUD Básico): Se você está fazendo um sistema que basicamente salva e recupera dados sem lógica complexa, DDD provavelmente é excessivo.
Time Pequeno e Inexperiente: DDD tem uma curva de aprendizado. Se seu time é pequeno e está começando, pode ser melhor focar em práticas mais básicas primeiro.
Prazos Muito Apertados: O DDD requer tempo para descoberta e modelagem. Se você tem uma deadline impossível, pode não ser o momento.
Ausência de Especialistas de Domínio: Sem pessoas que entendam profundamente o negócio, é difícil criar um modelo de domínio rico e correto.
Por que DDD Funciona?
O DDD funciona porque ataca problemas fundamentais do desenvolvimento de software:
1. Comunicação Entre Technical e Negócio
O maior problema em muitos projetos de software é a barreira de comunicação entre quem entende a tecnologia e quem entende o negócio. DDD quebra esta barreira ao criar uma linguagem comum.
2. Foco no que Realmente Importa
Em vez de começar pelo banco de dados ou pela interface, DDD força você a começar pela lógica de negócio. Isso garante que você está resolvendo os problemas certos.
3. Facilita Mudanças
Quando o código reflete como o negócio pensa, mudanças nas regras de negócio se traduzem mais naturalmente em mudanças no código.
4. Reduz Bugs de Lógica
Ao tornar as regras de negócio explícitas no código, DDD reduz a probabilidade de bugs causados por mal-entendidos sobre como o sistema deveria funcionar.
Exemplo do Mundo Real
Deixe-me compartilhar um caso real (anonimizado) que ilustra o poder do DDD:
Uma empresa desenvolvia um sistema de gestão hospitalar e enfrentava problemas constantes. Bugs em produção, funcionalidades que não faziam sentido para os usuários, e uma comunicação terrível entre desenvolvedores e médicos.
Antes do DDD:
- Desenvolvedores criavam entidades como
Person
,Event
,Item
- Médicos falavam sobre
Paciente
,Consulta
,Medicamento
- Cada conversa era uma sessão de tradução confusa
Depois de aplicar DDD:
Person
virouPaciente
Event
virouConsulta
ouCirurgia
dependendo do contextoItem
virouMedicamento
,Equipamento
ouExame
O resultado foi transformador:
- Redução de 60% nos bugs relacionados à lógica de negócio
- Médicos começaram a participar ativamente das reuniões técnicas
- Tempo de desenvolvimento de novas funcionalidades caiu pela metade
- Satisfação da equipe (técnica e médica) aumentou significativamente
Conclusão
Domain-Driven Design é fundamentalmente sobre colaboração. É sobre desenvolvedores e especialistas de negócio trabalharem juntos para criar um modelo que todos entendem e que se traduz diretamente em código mantível e correto.
O DDD não é uma bala de prata. Requer disciplina, tempo de aprendizado, e o contexto certo para brilhar. Mas quando aplicado adequadamente, pode transformar a forma como você desenvolve software, criando sistemas que realmente servem ao negócio e evoluem naturalmente com ele.
Nos próximos artigos desta série, vamos mergulhar profundamente em cada conceito apresentado aqui. Começaremos pela Linguagem Ubíqua, que é verdadeiramente a base de tudo no DDD.
Referências
- Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans
- Domain-driven design - Wikipedia
- Ubiquitous Language - Martin Fowler
- The Importance of Ubiquitous Language - VMware Tanzu
- Domain-Driven Design — The Ubiquitous Language - Medium
- Entenda DDD de uma vez por todas - André Darcie