Em determinado estágio de crescimento, a base de código deixa de fazer sentido de forma intuitiva. Lógicas que antes eram simples passam a estar espalhadas por vários serviços. Uma mudança no “perfil do usuário”, por exemplo, começa a afetar cinco partes diferentes do sistema, cada uma com uma definição diferente do que um “usuário” é. Isso acontece quando o modelo mental original e compartilhado do software deixa de corresponder à realidade do negócio que ele deveria sustentar. É nesse momento que começam a surgir conversas sobre a necessidade de um grande rewrite. Mas o problema raramente é apenas código difícil de manter ou mal delimitado. A raiz está em uma distância crescente entre a estrutura do software e a estrutura do negócio. É exatamente esse desalinhamento que o Domain-Driven Design tenta endereçar.
A Complexidade de Escalar Software
Quando uma empresa é pequena, mais ou menos todo mundo sabe como tudo funciona. A lógica de negócio está na cabeça das pessoas que começaram o time. À medida que novos times são adicionados e novas funcionalidades são entregues, esse entendimento compartilhado começa a se quebrar. Regras de negócio que antes eram conhecimento comum passam a ser implícitas, duplicadas ou até contraditórias quando implementadas em diferentes partes do código.
Isso começa a pesar dia a dia. O time de produto fica frustrado porque uma solicitação de feature aparentemente simples exige um esforço enorme de engenharia. Engenheiros passam a ser desacelerados porque precisam da aprovação de três times diferentes para fazer uma única mudança. O custo desse crescimento, guiado por features aparece de formas difíceis de quantificar, mas fáceis de perceber.
As consequências são práticas e difíceis de ignorar:
- O onboarding de um novo engenheiro leva meses porque nada está onde você esperaria que estivesse. Não dá para simplesmente apontar para um único serviço para entender uma capacidade de negócio.
- Cada release carrega um alto risco de regressão. Uma mudança no módulo de billing pode quebrar algo em analytics por causa de uma dependência que ninguém conhece bem.
- A capacidade de responder a mudanças de mercado desacelera drasticamente. Pivotar ou lançar uma nova linha de produto vira um grande esforço, porque o sistema existente é um emaranhado de responsabilidades interconectadas, mas logicamente separadas.
Quando o Código Deixa de Refletir o Negócio (e Como o DDD Ajuda)
O DDD ajuda a fechar a distância entre o software e o negócio que ele sustenta. Ele muda o foco de preocupações puramente técnicas, como esquemas de banco de dados e endpoints de API, para a modelagem do próprio domínio de negócio. Você começa perguntando “o quê” e “por quê” antes de pular para o “como”. O objetivo é que o software reflita de forma explícita e utilizável a realidade do negócio por trás dele.
Isso força conversas que deveriam ter acontecido desde o começo. Em vez de um engenheiro assumir como funciona um “desconto para cliente”, o time passa a conversar com quem realmente entende do assunto, como pessoas de marketing ou vendas. Essas conversas deixam claras as regras e os limites reais do negócio. Um “Produto” no contexto de estoque, por exemplo, não é a mesma coisa que um “Produto” no catálogo de marketing. Reconhecer essas diferenças e modelá-las de forma explícita é o que está no centro do DDD.
Adiar esse tipo de clareza arquitetural só piora o problema. Cada nova feature construída sobre limites de domínio pouco claros adiciona mais uma camada de complexidade, tornando as mudanças futuras mais caras e mais lentas. Você acaba com um sistema que vive em conflito constante com o negócio que ele deveria viabilizar.
Como Adotar Domain-Driven Design
A boa notícia é que você não precisa de um projeto de seis meses nem de uma paralisação completa do sistema para começar a se beneficiar do DDD. Ele pode ser adotado de forma incremental, começando pelas áreas que mais causam dor.
Começando Pequeno ao Identificar Seus Bounded Contexts Principais
O primeiro passo é identificar seus Bounded Contexts. Eles são os limites lógicos dentro do negócio onde um modelo específico se aplica. Um “Ticket de Suporte” tem um significado claro e um conjunto de regras dentro do contexto de atendimento ao cliente, mas esses detalhes são irrelevantes para o contexto de billing.
Você pode descobrir esses contextos realizando workshops colaborativos com especialistas de domínio de diferentes áreas da empresa. Olhe para o código que já existe e relacione cada parte a uma capacidade clara do negócio. Quais partes do código lidam com envio? Quais lidam com autenticação de usuários? Esses recortes normalmente são bons pontos de partida. Comece por um ou dois contextos que sejam críticos para o negócio e que hoje estejam tornando o desenvolvimento mais lento ou confuso.
Criando uma Linguagem Comum Entre os Times
Depois de identificar um Bounded Context, a coisa mais valiosa que você pode fazer é estabelecer uma Linguagem Ubíqua. Trata-se de um vocabulário compartilhado de termos com os quais todos, de engenheiros a product managers e stakeholders de negócio, concordam. Quando um engenheiro diz “Shipment” e um especialista em logística diz “Shipment”, ambos estão falando exatamente do mesmo conceito, com as mesmas regras e comportamentos definidos explicitamente no código.
Essa linguagem compartilhada deve ser usada em todos os lugares: em conversas, na documentação, nos nomes de variáveis e nos nomes de classes. Ela elimina ambiguidades e garante que o modelo de software reflita com precisão o domínio de negócio.
Aplicando Padrões à Medida que o Sistema Evolui
Dentro de um Bounded Context bem definido, você pode começar a aplicar padrões táticos de DDD para reforçar regras de negócio e manter a consistência.
- Aggregates agrupam objetos relacionados em uma única unidade que pode ser tratada como um todo. Por exemplo, um aggregate
Orderconteria objetosOrderLineIteme seria responsável por garantir invariantes, como assegurar que não é possível adicionar um item a um pedido já pago. O aggregate root (Order) é o único ponto de entrada para modificações, o que mantém o modelo consistente. - Value Objects representam aspectos descritivos do domínio que são identificados pelo seu valor, e não pela sua identidade, como um objeto
MoneyouAddress.
Para sistemas existentes, o padrão Strangler Fig é uma forma de introduzir esses conceitos de uma maneira incremental. Você pode construir um novo serviço que modele corretamente um único Bounded Context e, aos poucos, redirecionar chamadas do monólito antigo para esse novo serviço. Com o tempo, é possível desativar partes do sistema legado de forma segura, sem um rewrite arriscado em “big bang”.
Esse processo funciona melhor quando é iterativo e guiado pelo time. Dê autonomia a um único time multifuncional para ser dono de um domínio de ponta a ponta. Eles se tornam os especialistas e têm autonomia para modelá-lo corretamente com base em feedback dos especialistas de domínio.
Um Checklist para Seus Primeiros Passos com DDD
- Inicie uma fase de descoberta. Realize uma sessão com engenheiros e pessoas de produto para mapear as áreas mais dolorosas da base de código e identificar possíveis limites de domínio.
- Forme um pequeno time multifuncional em torno do que parece ser um único Bounded Context. Inclua um especialista de domínio do lado do negócio, se possível.
- Defina e documente seus termos iniciais da Linguagem Ubíqua. Comece um documento compartilhado com os primeiros 5 a 10 termos centrais do contexto escolhido e garanta que todos concordem com seu significado preciso.
- Identifique pelo menos um Aggregate root no contexto escolhido e mapeie seus invariantes (as regras de negócio que ele sempre precisa proteger).
- Planeje mudanças incrementais. Use um padrão como o Strangler Fig para construir uma pequena parte de funcionalidade bem modelada que possa substituir um trecho do sistema antigo, validando a abordagem antes de assumir uma mudança maior.