O que é domain-driven design?

Prévia grátis 7 min de leitura

O problema que o DDD resolve

Você já construiu aplicações Rails, já escalou essas aplicações, já projetou sistemas que atendem milhões de requisições. Mesmo assim, toda base de código que cresce acaba batendo na mesma parede: o código deixa de refletir o negócio.

Um model User tem 80 métodos. Um controller cria matrículas, envia emails, calcula XP e valida senhas, tudo na mesma action. Alguém pergunta "onde fica a lógica de matrícula?" e a resposta é "em todo lugar".

Não é um problema de escala nem de performance. É um problema de modelagem: o software se afastou do negócio que deveria representar.

Domain-Driven Design (DDD) é um conjunto de princípios, apresentado por Eric Evans num livro de 2003, para estruturar o software de modo que o código espelhe o domínio do negócio. O código fala a mesma língua que o negócio, e as fronteiras do código batem com as fronteiras da organização. Quando o negócio diz "matrícula", existe no código um namespace Enrollment que é dono desse conceito.


Linguagem ubíqua: a base de tudo

A ideia mais subestimada do DDD é também a mais importante: a linguagem ubíqua (ubiquitous language).

Linguagem ubíqua significa que desenvolvedores, gerentes de produto e especialistas do domínio usam os mesmos termos, e esses termos aparecem no próprio código: nos nomes de classes, métodos e módulos, não em comentários.

Pense neste LMS. O negócio fala de:

  • Cursos e aulas (o que os alunos aprendem)
  • Matrículas (um aluno entrando num curso)
  • Submissões (código que um aluno envia para avaliação)
  • Pontos de experiência (XP ganho por concluir atividades)

Agora olhe a base de código:

# O negócio diz "Catalog" — o código diz "Catalog"
Catalog::Course
Catalog::Lesson

# O negócio diz "Enrollment" — o código diz "Enrollment"
Enrollment::Enrollment
Enrollment::LessonCompletion
Enrollment::ExperiencePoint

# O negócio diz "Execution" — o código diz "Execution"
Execution::Submission

Quando um gerente de produto diz "um aluno se matricula num curso", qualquer desenvolvedor pode apontar para Enrollment::StartCourseService e dizer "é esta operação aqui". Não há camada de tradução nem conversão mental entre o que o negócio fala e uma estrutura de código arbitrária.

No nível sênior, a linguagem ubíqua é mais do que uma convenção de nomes. Ela mantém o código e o negócio alinhados. Quando o código usa palavras diferentes das do negócio, os bugs se escondem nessa diferença. "User" no contexto de identidade quer dizer credenciais de login. "User" em matrícula quer dizer um aluno com progresso. Chame os dois de User sem separar os contextos e você vai misturar responsabilidades que deveriam ficar separadas. É assim que o User vira o monstro de 80 métodos.


DDD estratégico vs tático

O DDD atua em dois níveis, e é comum os times confundirem os dois.

DDD estratégico cuida da visão geral:

  • Onde ficam as fronteiras entre as partes do sistema?
  • Quais partes do domínio diferenciam o negócio da concorrência?
  • Como as partes do sistema conversam entre si?

DDD tático cuida dos padrões de implementação:

  • Entidades, objetos de valor, agregados
  • Serviços de domínio, serviços de aplicação
  • Repositórios, factories
flowchart TB
    subgraph Strategic["DDD Estratégico"]
        direction TB
        StrategicDesc["Bounded Contexts, Mapas de Contexto, Core/Suporte/Genérico"]
        subgraph Tactical["DDD Tático"]
            TacticalDesc["Entidades, Objetos de Valor, Serviços, Agregados"]
        end
    end

A maior parte do valor está no nível estratégico. Acertar as fronteiras pesa mais do que decidir entre um padrão Repository ou chamar o ActiveRecord direto. O grande monolito Rails do Shopify funciona porque as fronteiras dele são boas, e não porque cada classe segue os padrões táticos do DDD.

O GitHub organiza os times por áreas de produto (Actions, Codespaces, Copilot), cada uma com dono claro. O Basecamp mantém um monolito, mas dividido em áreas com responsabilidades bem definidas.


O domínio do LMS: um exemplo vivo

Este LMS é construído com DDD. O domínio se divide assim:

Domínio Namespace Responsabilidade
Identity Identity:: Quem é o usuário? Credenciais de autenticação, sessão atual
Auth Auth:: Como o usuário prova quem é? Login, cadastro, política de senha
Catalog Catalog:: O que dá para aprender? Cursos, aulas, desafios
Enrollment Enrollment:: O que o aluno já fez? Progresso, conclusões, XP
Execution Execution:: Que código o aluno enviou? Submissões, resultados
Admin Admin:: Como os administradores gerenciam o conteúdo? Operações de CRUD
Public Public:: O que um visitante vê? Marketing, páginas de entrada
Community Community:: O que os alunos estão comentando? Threads de comentários nas aulas
Partners Partners:: Quem patrocina ou contrata? Patrocínios, contatos de parceiros, indicações, compartilhamento de certificados

Cada domínio é dono da sua lógica. Auth::PasswordPolicy define as regras de senha, e o model não as repete. Enrollment::StartCourseService cuida da matrícula, e o controller não. Enrollment::UserGamificationQuery calcula o XP, então não existe um método user.total_xp vazando de um domínio para outro.

Isso é DDD na prática: dono claro, linguagem clara, fronteiras claras.


Por que o DDD pesa mais conforme o sistema cresce

Aplicações pequenas não precisam de DDD. Um projeto de fim de semana com 5 models e 3 controllers vai muito bem com as convenções padrão do Rails. Os models são pequenos, a lógica é óbvia e todo mundo entende tudo.

Só que sistemas crescem, e crescem em duas direções ao mesmo tempo:

  1. Complexidade: mais regras de negócio, mais casos-limite, mais integrações
  2. Tamanho do time: mais desenvolvedores, mais opiniões, mais mudanças em paralelo

Sem DDD, o crescimento vira um emaranhado em que tudo depende de tudo. Mexer na lógica de matrícula quebra as notificações porque as duas compartilham models. Criar um novo tipo de curso obriga a editar 14 arquivos em 6 diretórios.

Com DDD, o crescimento soma em vez de embaraçar. Cada domínio evolui por conta própria. O time de matrícula mexe no código de matrícula, e o time de catálogo mexe no código de catálogo. Quando precisam interagir, passam por interfaces definidas, sem mexer nas entranhas um do outro.

A maior parte do custo de um software está em entendê-lo seis meses depois, não em escrevê-lo. O DDD otimiza esse entendimento.


Erros comuns

Começar pelos padrões táticos antes de entender o domínio

Entidades, repositórios e factories vêm primeiro, e o domínio fica para depois. O resultado é um código bem estruturado que modela a coisa errada: um repositório elegante não ajuda se os bounded contexts estiverem errados.

Em vez disso, mapeie o domínio com quem conhece o negócio. Descubra primeiro a linguagem, as fronteiras e os conceitos centrais. Os padrões táticos vêm depois.

Tratar o DDD como tudo ou nada

O time aplica DDD em tudo, inclusive em telas simples de CRUD, e se afoga em cerimônia.

Em vez disso, aplique o DDD estratégico em todo lugar, porque boas fronteiras ajudam tudo. Aplique o DDD tático com critério, só no domínio central, onde a lógica de negócio é complexa.

Confundir DDD com microsserviços

DDD trata de fronteiras lógicas, não de fronteiras de deploy. Um monolito Rails com módulos bem definidos, como este LMS, é um ótimo lugar para o DDD.

Em vez disso, use DDD dentro do monolito. Extrair um microsserviço é outra decisão, para quando você precisar fazer deploy de uma parte de forma independente.


Resumo

Conceito O que guardar
Domain-Driven Design O software deve modelar o domínio do negócio: mesma linguagem, mesmas fronteiras
Linguagem ubíqua Desenvolvedores e especialistas do negócio usam os mesmos termos, e o código também
DDD estratégico Desenhar as fronteiras entre as partes do sistema; é onde está a maior parte do valor
DDD tático Padrões de implementação (entidades, objetos de valor, serviços); use com critério
Os domínios do LMS Identity, Auth, Catalog, Enrollment, Execution, Admin, Public, Community, Partners; cada um é dono da sua lógica
O problema do crescimento Sem fronteiras, a complexidade vira um emaranhado em que tudo depende de tudo
DDD não é microsserviços O DDD define fronteiras lógicas; fronteiras de deploy são outra decisão
Aula 1 de 7 · grátis

Continue com a aula 2

Bounded contexts e mapas de contexto

Explica bounded contexts, os sinais que indicam suas fronteiras e os padrões de mapeamento entre os nove contextos do LMS.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...