Versão revisada em inglês aqui: REPLACEME

Fala, turma! Espero que esteja tudo bem.

Tenho estudado mais profundamente DDD nas últimas semanas,e estou trabalhando em um template de um monorepo typescript e comecei a aplicar meus conhecimentos lá. Eu ja tinha estudado a teoria e até comecei o livro azul algumas vezes, mas nos vários momentos, não investi energia suficiente e nem fui muito além da teoria.

Também começamos internamente na Codeminer42 alguns encontros pra discutir design, arquitetura e padrões de projeto e, felizmente casou demais com esse momento.

E o que é o tal DDD?

Escutei em algum lugar que desenvolvimento de software é como construir castelos de areia no ar, de alguma forma vamos ter representações do mundo real no nosso código, em grande ou pequena escala, isso não importa. Conforme avançamos em nossas carreiras, percebemos que os problemas mais difíceis de se resolver são o que tange a comunicação e o entendimento do negócio e seus domínios. E como desenvolvedores, no geral, gostamos de passar a maior parte do tempo escrevendo código (acredito que inicialmente nas nossas carreiras essa abordagem é positiva, porque precisamos de uma quantidade maior de ferramentas/experiências dentro da nossa caixinha), mas quanto mais pensamos assim, menos focamos em como os problemas precisam ser desenhados e planejados. É aquela história de afiar o machado por mais tempo do que se está cortando a árvore…

Então se, por exemplo, vamos trabalhar em um sistema de vendas online para artigos de pesca. Precisamos ter uma figura (ou mais de uma) com conhecimento sobre esse domínio, um Especialista assim por dizer.

Então se vamos iniciar esse projeto hoje, precisamos focar em qual domínio iremos operar. Tendo isso em mente, conseguimos guiar o nosso desenvolvimento em harmonia com o domínio e representa-lo (parcialmente ou não) com a quantidade necessária de características para resolver o problema.

É importante que seja possível que uma pessoa nova no projeto consiga ler o código e adquirir conhecimento sobre artigos de pesca em como desenhamos nosso modelo do domínio. E vamos represetar esse domínio (obviamente) através de uma abstração com o nosso código.

Tendo o Especialista trabalhando lado a lado com o time é a única forma de obtermos sucesso, conforme obtemos esse conhecimento “crú” como podemos abstrair esse modelo que pouco a pouco vamos contruindo, e até o momento, apenas em nossas cabeças para o código? E nesse momento, com certeza o que eu tenho na minha cabeça difere do restante da equipe, provavelmente do que o próprio especialista passou e do que o “real” realmente representa.

Nessa conversa inicial o papo começa num tom como: | “Bem, nós somos o maior distribuidor de artigos de pesca do MUNDO, até hoje usamos sempre sistemas terceiros. Nós vendemos varas, molinetes, iscas, sabe… coisas de pesca. As pessoas vêm aqui procurando varas para lambari, mas isso não é bem uma coisa… quer dizer, você pode usar qualquer vara para lambari, mas algumas são melhores… E nem me fale de licenças, algumas pessoas precisam delas, pode depender da época de reprodução e de onde moram, mas pode ser legal, podemos centralizar e tercerizar esse tipo de serviço…”

Bom, de certa forma começamos a entender o nosso “domínio” mas não conseguimos nesse instante traduzir isso para o código (e nem deveriamos). E como representar essa abstração? Na forma de um modelo desse domínio, uma forma organizada daquele conhecimento. E conforme iniciamos essa representação percebemos que ele se torna (em sua maioria) algo muito grande para se segurar com as mãos, então precisamos quebra-lo em pedaços melhores e categoriza-los, talvez nem precisamos de todas aquelas partes sendo representadas. Teremos clientes de várias partes do mundo, então sabermos da sua localidade parece algo importante, mas num primeiro momento, saber sobre o gosto musical deles não parece necessário.

“0 que faz o peixe quando pedem piadas pra ele?”

“Não basta ir ao rio com vontade de pescar, é preciso levar rede.” Então, temos que encontrar uma maneira de comunicarmos esse modelo entre todas as partes envolvidas, pessoas desenvolvedoras, designers, de produto e os especialistas, para que não haja ambiguidades. Quanto mais entendimento nesse momento, menos paredes temos que demolir no futuro.

DDD vai combinar design e praticas de desenvolvimento e demostrar como elas podem caminhar lado a lado para termos bons produtos.

Construindo nosso Conhecimento do Domínio

Sabemos então que vamos construir esse sistema para a maior loja de artigos de pesca do mundo, então já começamos a construir essa base de conhecimento. Trouxemos para essa reunião (e muitas outras) esses especialista do domínio, temos um pescador experiente que é um dos sócios, um advogado que trabalha com licenças pelo mundo, um ictiologista, um representante de motores etc. Nenhum deles com conhecimento sobre desenvolvimento de software, e cabe a nós conseguirmos extrair o que é necessário.

Temos que começar de algum lugar… Começamos com esboço das abstrações do papo até agora:

Produtos: Varas, Molinetes, Iscas, Linhas, Acessórios, Rações ...
Clientes compram produtos
Produtos com regras de compatibilidade

Licenças: Amadora, Permanente, Desembarcada, Embarcada, Comercial ...
...

Especialista: “Queremos que o sistema seja ‘inteligente’! Podendo indicar e alertar sobre compatibilidade numa mesma compra.” Você: “Certo! Me fale um pouco mais sobre essas compatibilidades…” Especialista: “Bem, uma vara pesada precisa de linha pesada e iscas grandes. Você não pode colocar uma isca pequena para truta em uma vara de robalo, ela não vai fisgar direito. E molinetes não funcionam bem com iscas pesadas…” Você: “O que torna uma vara pesada ou leve?” Especialista: “É a potência e a ação. Potência é a força necessária para dobrar a vara, ultraleve, leve, média, pesada…”

Então tomamos algumas notas:

Sistema de Compatibilidade de Equipamentos:
- A vara tem potência (ultraleve → pesada) e ação (rápida → lenta)
- Isso determina a faixa de peso da isca e a faixa de teste da linha
- As carretilhas têm capacidade e tipo de linha (girando vs. arremessando)
- A técnica de pesca influencia a seleção do equipamento
- A espécie-alvo influencia a seleção da técnica

Uffa, então nesse momentos também podemos representar esse modelo não só como um diagrama mas também como uma ideia: “A seleção bem-sucedida do equipamento de pesca é determinada pela correspondência das características do equipamento com as técnicas de pesca, que são escolhidas com base nas espécies-alvo e nas condições ambientais, com regras de compatibilidade garantindo que os equipamentos funcionem efetivamente em conjunto.”

Pode ser que nesse momento queiramos montar um protótipo referente ao que já temos, e é muito importante entender que esse trabalho não é uma via de mão única, os especialista envolvidos precisam também de feedback e boas perguntas para que seja possível essa extração de conhecimento da melhor forma possível.

Cada vez mais elementos podem ir sendo adicionados ao entendimento: Os padrões sazonais afetam a seleção de iscas, as condições da água influenciam a escolha da técnica e o nível de habilidade do pescador limita a complexidade do equipamento…”

Ubiquidade o quê?

Mencionei que talvez logo depois de um entendimento inicial, os desenvolvedores podem querer prototipar algo, porque assim é a nossa natureza! Mas é muito importante lembrar que, é apenas um protótipo para que o entendimento comum de todas as partes se aproxime mais e mais (e como um bom protótipo, possa ser descartado). Citando de novo a nossa natureza, é inevitável que na nossa cabeça pipoquem classes, métodos, algoritmos etc, enquanto toda a conversa se extende e não acho que isso seja ruim, mas até esse momento esbarramos possívelmente por muitos termos e conceitos pela primeira vez e do outro lado da mesa, nada desses relacionados ao desenvolvimento de software passou pela cabeça deles e, então como podemos falar a mesma língua? Como evitamos que o entendimento sobre algo se centralize e seja claro?

O quão difícil é explicar um conceito da computação para alguém totalmente leigo? Já viu algum colega mandando “pedaços” de código em um chat com pessoas de produto? O quão desastroso é isso? Tendemos a usar nosso próprio dialeto quando estamos em um devido contexto (escrevendo esse artigo, percebi o quanto é dificil evitar palavras em Inglês para certos termos, porque no dia-a-dia a maior parte da comunicação é em Inglês e de certa forma boa parte do material e fontes de conhecimento da nossa área são também nessa língua).

Um princípio fundamental do DDD é usar uma linguagem baseada no modelo. Como o modelo é o ponto de contato, o ponto onde o software encontra o domínio, é apropriado usá-lo como base para essa linguagem. Então à partir disso, tentamos utiliza-la em todos pontos de contato possíveis, na escrita do código, na nossa documentação, diagramas, na fala com o cliente etc.

A Linguagem ubíqua conecta todas as partes desse design e orquestra essa boa comunicação e entedimento. Com certeza é um trabalho árduo e (talvez até) sem fim, conforme o time se entende e evolui nesse design. Um dicionário vai sendo criado para determinar cada qual no projeto e é muito importante entender quais termmos podem inclusive estar sendo usados para representar a mesma coisa, ambiguidades ou inconsistências podem nos levar a estruturar coisas de maneira errada.

E a linguagem só será ubiqua dado um contexto, se não temos limites nos nossos contextos. Num primeiro momento, eu pensei que então aplicar DDD em todo lugar no projeto seria a melhor abordagem, mas na verdade temos que analisar onde vamos realmente nos beneficiar da implementação.

Perceberam que nas simulações de conversas já descritas aqui, de certa forma conseguimos já identificar e construir nossa linguagem, mas obviamente, na vida real esse dialogo é muito mais verboso e complexo (inclusive adicionei mais detalhes a UML lá em cima para termos mais o que analisar).

Só pensando ainda nos equipamentos de pesca, podemos representar algo como:

# Domínio de Equipamentos de Pesca

## Tipos de Equipamentos
- Vara de Spinning: projetada para molinetes, com guias para baixo
- Vara de Casting: projetada para molinetes de baitcasting, com guias para cima
- Molinetes de Spinning: Molinete fixo, mais fácil para iniciantes
- Molinetes de Arremeso: Molinete giratório, arremessos mais precisos

## Conceitos de Negócios
- Combinação de Equipamentos: Conjunto completo e compatível de vara + carretel + linha + iscas
- Espécies-Alvo: A espécie de peixe que o pescador deseja capturar
- Técnica de Pesca: O método de pesca (arremesso, trolling, pesca com mosca)

## Regras
- Varas de Spinning devem ser pareadas com molinetes
- Varas de Arremeso devem ser pareadas com molinetes de baitcasting
- A potência da vara deve ser compatível com a faixa de peso da isca
...

Perceberam também o quão dificíl começa a se tornar a leitura da UML quanto mais conceitos vão aparecendo? Ao mesmo tempo, se mantivermos uma documentação centralizando toda a nossa linguagem o quão rápido ela vai se tornar obsoleta? Se torna igualmente importante o momento em que transformamos o conhecimento adquirido dos Especialista para o desenvolvimento do software, termos um ótimo modelo não necessiaramente transforma esse momento e o produto final com qualidade semelhante.

Durante a fase de desenvolvimento, com certeza vão ter momentos que conceitos ou relações vão se mostrar incompletas, mesmo nós tendo investido tempo suficiente com os Especialista, então, quero salientar mais uma vez que esse trabalho de feedback e obtenção e esclarecimento de dúvida e conceitos nunca tem fim. Se de alguma forma tentamos criar caminhos imaginários ou atalhos entre a linguagem contextual que temos em mãos, com certeza vamos falhar em manter consistência da mesma! Nunca devemos presumir que algo que parece óbvio realmente é:

  • Presumir que palavras familiares têm significados familiares;
  • Não se aprofundar na mecânica ou regra de negócio;
  • Confirmar suposições em vez de explorar o domínio.

Com certeza, ainda temos todas as outras preocupações que tangem o desenvolvimento de software em si, mas se falharmos nesse momento que estamos traduzindo a linguagém para o código, torna-se as abstrações fragéis e difíceis de se mudar, qualquer maré alta desmorona o nosso castelo…

Desenvolvedor: “Então você tem varas leves e varas pesadas…” Especialista: “Sim, potência leve para trutas, potência pesada para robalos, por exemplo…” Desenvolvedor: “Entendi, então varas mais pesadas para peixes maiores.” Especialista: “Exatamente!”

Se nesse momento presumirmos que na verdade uma vara com potência pesada está atrelada também a ao peso físico da mesma, estaremos cometendo um erro.

Desenvolvedor: “Espera aí, você disse que esta vara de alta potência é mais leve que a vara de baixa potência?” Especialista: “É, a potência tem a ver com a rigidez, não com o peso. Uma vara de alta potência não entorta muito, mesmo com um peixe grande puxando. Uma vara de baixa potência entorta facilmente.” Desenvolvedor: “Então ‘potência’ tem a ver com… rigidez?” Especialista: “Certo! É a espinha dorsal da vara. Potência pesada para peixes que lutam muito, potência leve para peixes pequenos, para que você possa sentir a mordida deles.”

Entendem que presumir algo é sempre um erro? E na verdade, esse trabalho de conseguirmos obter melhores respostas sobre o domínio caí mais sobre as nossas costas do que do Especialista. É mais sobre fazer as perguntas certas e com certeza, esse processo se torna mais robusto quanto mais fazemos isso.

Um domínio pode ser expressado com vários modelos e cada modeló pode ser expressado de inúmeros jeitos no nosso código, com certeza, ainda que dependemos muito de como a linguagem e framework que estamos utilizando consegue abstrair camadas e complexidade a nível de código, mas hoje eu quero evitar falarmos sobre código.

O que se acontece frequentemente é que quem vai escrever o código, não exatamente participou do design/modelagem desse modelo e nesse ponto, a linguagem pode começar a se perder, se qualquer decisão a nível de código muda como o modelo representa algo, o modelo também deveria ser modificado, entendem? Não pode haver disparidade entre a linguagem representada no modelo e no código e tendo essas pessoas distantes de cada ponta, com certeza impacta em uma linguaguem ubiqua que perde força. É sempre uma mão de via dupla entre a representação do nosso modelo do domínio de conhecimento e a implementação dela. Um deve projetar sombra no outro, cada decisão e descoberta no código influência na estrutura do dóminio, e vice versa.

É bem fácil falhar

Tentei deixar claro que cada modelo tem um contexto e enquanto trabalhamos com apenas um, o contexto é implicito e mais “fácil” de se entender, mas uma aplicação real vai iteragir com mais código, e muitas vezes até externa a nossa codebase.

Como defínimos e delimitamos onde uma modelo termina e o outro começa? Não existe uma mágica para dividir um modelo grande em modelos menores. Talvez um bom Norte seja incluir os elementos no modelo que são relacionados ao conceito natural, ele deve ser pequeno e independente o suficiente para que possamos atribui-lo uma equipe.

Chamamos de Contexto Delimitado o padrão de estrategicamente definir os limites entre cada modelos e times. Ficou claro que não podemos ter contradições internas no modelo, então a melhor forma é dividirmos as resposanbilidades da melhor forma possível.

Você: “Então, quando um cliente procura por um ‘produto’…” Gerente de Catálogo: “Eles estão procurando equipamentos de pesca com especificações.” Gerente de Estoque: “Espere, eu chamo isso de ‘itens’ - coisas cujas quantidades eu monitoro.” Especialista: “Eu penso neles como ‘componentes de equipamento’ que podem ser combinados.” Gerente de Vendas: “Para mim, são ‘SKUs’ com preços e promoções.”

O mesmo conceito, com nomes e preocupações diferentes! Podemos mapear em algo como:

1. Contexto do Catálogo de Equipamentos
Preocupação: Gerenciar especificações e compatibilidade de equipamentos de pesca
Possuem:
- Equipamentos de Pesca (não "Produto")
- Regras de Compatibilidade
- Especificações Técnicas
- Categorias de Equipamentos

2. Contexto da Recomendação
Preocupação: Adequar o equipamento aos cenários de pesca e às necessidades do pescador
Possuem:
- Componente do Equipamento (não "Produto")
- Pescador (não "Cliente")
- Combinação de Equipamentos
- Cenário de Pesca

3. Contexto de Vendas
Preocupação: Listagens de produtos, preços e operações de e-commerce
Possuem:
- Lista de Produtos (não "Equipamentos")
- SKU
- Preço
- Campanha Promocional

4. Contexto do Estoque
Preocupação: Gestão de estoque, armazenagem e atendimento
Possuem:
- Item em estoque (não "Produto" ou "Equipamento")
- Nível de estoque
- Localização do depósito
- Reposição

Dessa forma se conseguimos delimitar com clareza cada limite e cada time pode trabalhar de forma mais independente possível (por favor, não tomem isso como se não fosse necessário continuar existindo comunicação).

  • A equipe de catálogo se concentra nas especificações técnicas;
  • A equipe de recomendação se concentra na eficácia da pesca;
  • A equipe de vendas se concentra na conversão e no preço;
  • A equipe de estoque se concentra no atendimento e no estoque.

O ensinamento principal é cada contexto tem seu próprio modelo de domínio que atende às suas necessidades específicas. Entenda muito bem cada CONTEXTO.

Modelo de Domínio Anêmico

Agora pensando que iniciamos em um novo projeto hoje, no geral, não temos o contexto de como e porque as coisas foram escritas de certa forma, ainda mas se nesse caso a falta de informações e clareza do próprio código traz mais dúvidas do que certeza. Um sentimento que sempre nos atinge é: “Eu gostaria de reescrever tudo isso do zero!”.

Na maioria das vezes nem uma apresentação de “Qual a intenção do projeto?” nos é dada. Quantas vezes resumimos em algo como “são basicamente só CRUDs”. E sinceramente, se um projeto se resume a apenas modelagem de migrations para o banco, provavelmente alguém entendeu algo muito errado (não estou dizendo que não existem projetos que podem se resumar “apenas” a isso, mas dificilmente são).

Esse modelo fraco e sem estrutura, vai se parecer com um modelo real, pode até ter abstrações e código mais fácil de se mudar e com certeza vamos nos beneficiarmos disso na maiorias dos projetos, mas com isso, nós adicionamos em alguns momento mais complexidade do que talvez o necessário mas deixamos de manter limites e estratégias que nos permiter manter o modelo e o código (ou que é que seja) ainda falando a mesma linguagem.

Dores de cabeça pelas abstrações

Tentei me repetir que, no fim das contas temos nos beneficiar da comunicação. É sobre ouvir e ser ouvido, não manter isso numa via de mão única (talvez inclusive seja por isso que as LLMs não vão roubar o trabalho de bons arquitetos). As pessoas não vão nos responder algo como “Você está absolutamente correto!” em cada passo de um dialógo (ou pelo menos não deveriam).

Dar um passo atrás sempre que possível e (mais uma vez) manter o carrapato da dúvida longe do nosso domínio, morrendo de fome sem sangue derramado das ambiguidades.

  • “O que acontece antes disso?”
  • “O que acontece depois disso?”
  • “Quem mais se importa com esse conceito?”
  • “Em que situações isso se comportaria de forma diferente?”
  • “Quais propriedades X tem que Y não tem?”
  • “Quando você usaria esta palavra em vez daquela?”
  • “Etc”

É muito claro que se só tivessemos também aceitado o entedimento sem questionamento nenhum, iriamos então desenhar e aplicar essa verdade no código, ficando distante do que a representação real deveria ser (mais uma vez). E se no fim das contas, temos só camadas e mais camadas de abstrações que não se alinham fielmente com o domínio, é só dor de cabeça e mais linhas de códigos para manutenção e (com certeza) demolição no futuro… E não me entenda mal, o código não deve ser escrito em pedra, longe disso, mas se não nos mantivermos fieis a nossa linguagem ubiqua, todo o suor vai ter sido em vão.

Níveis corretos de isolamento

Talvez mais e mais vamos esbarrar com sistemas legados que precisam ser consumidos de alguma forma, mas as regras e abstrações la dentro não condizem muito com o que é esperado, tanto a nível técnico, quanto de nomenclaturas (imaginem que ele pode inclusive ter sido escrito numa língua diferente dos outros projetos no nosso cliente). Como isolar isso pra podermos então abstrai-lá e então consumirmos o que é necessário para o nosso entendimento e os padrões do nosso projeto?

Seguindo com o exemplo, temos que nos conectar agora com um sistema de Inventório dos nossos artigos de pesca bem antigo (escrito em COBOL). Num primeiro momento, sentimos que o vocabulário é confuso, e não temos nenhum desenvolvedor “nativo” dessa codebase para nos ajudar.

ITEMREC - Item record/registro do item talvez?
QTYONHAND - Quantity on hand/Quantidade disponível, com certeza
QTYCOMMIT - Committed quantity/Quantidade comprometida, o que isso quer dizer?
QTYHELD - Held quantity/Quantidade mantida, qual a diferença com o em mãos?

Se simplesmente aceitarmos e consumirmos essas verdades do outro sistema de forma direta, vamos poluir o nosso domínio.

Com certeza o melhor antes de seguir com a codificação seja trazer pelo menos um Especialista, já que o conhecimento sobre o código se perdeu (e o que está escrito não nos diz muito).

Você: “Então, recebemos a quantidade disponível e a quantidade comprometida do sistema antigo…” Especialista: “Espera aí, o que é ‘quantidade comprometida’? Não usamos esse termo.” Você: “É assim que o sistema antigo chama…” Especialista: “Ah! Você quer dizer Estoque reservado! É o estoque que prometemos aos clientes, mas ainda não enviamos.” E a conversa segue…

Nesse momento, podemos construir uma Camada de Anticorrupção, além de entendermos, nós precisamos “traduzir” esses termos legados para o nossa linguagem e evitarmos que respingue no nosso domínio.

Se temos abstrações de repositórios que dependem de outras para funcionar, e ainda pior, estão atrelados ao conhecimento externos já estamos falhando com o nosso desacoplamento.

Quanto do nosso entendimento pessoal estamos colocando na ponta dos nossos dedos e quando alguém novo entra no projeto, talvez temos que explicar como algumas nuancias funcionam, porque fizemos algumas traduções dos requerimentos ao invés de realmente utilizarmos da linguagem ubiqua.

E os nossos domínios não podem ter dependência de nada exterior. E não só elas, se por algum motivo nosso repositório antes lidava com um tipo especifico de dado ou se conectava ao um tipo especifico de banco, o que acontece caso isso precisa mudar e não definimos as fronteiras onde elas realmente deveriam estar?

A dívida técnico nunca é paga

Comentei sobre construirmos castelos de areia no ar, porque nosso trabalhamos em construir software em suma pode ser tão abstrato e no fim, cada grão de areia que representa o conhecimento e entendimento ali depositado, pode de certa ser usado para construir outro castelo ou figura na areia amanhã. Derrubar paredes e construir novas pontos nesse nosso castelo é menos custoso que o fazer (na sua maioria), na vida real, com paredes de concreto e pontes de ferragem.

Nós sabemos que cada linha de código que escrevemos carrega consigo a necessidade de ser revisitada no futuro. E não necessariamente porque cometemos erros propositais, mas porque nosso entendimento do domínio e das técnicas empregadas evoluem. Muitas vezes essa dívida é inclusive sabida enquanto a enviamos para produção, mas por conta de motivos de negócio ou outros quaisquer, não podemos paga-la imediatamente, e sinceramente, esse nem é o problema. O problema é que essa dívida nunca é paga, em suma, nunca é um bom momento para voltarmos em um “código pronto”, mas nos sabemos o quanto essa dívida pode crescer e nos perseguir, cedo ou tarde.

O melhor dos mundos, é combinar, até de antemão, momentos em que o time pode focar para pagar essas dívidas. Uma vez por semana? Uma vez por Sprint? Uma task por mês? De alguma forma tem que ser feita. Isso sem pensar em débitos que vão surgir, dependentes do avanços das tecnologias empregadas e não só da evolução do nosso domínio e entendimento.

Bom, perceberam que muito do que abordamos aqui hoje, não tem só relação com DDD e sim de como podemos tomar boas decisões no dia-a-dia, com (mais uma vez) muita comunicação e pitadas de pragmátismo.

Deixando claro que isso é um recorte dos meus estudos!

Se algum especialista em pescas encontrar alguma atrocidade cometida da minha parte em relação as terminologias, por favor, continue a nadar.

Todas as reflexões aqui foram enquanto eu consumia esses materias:

Nos vemos no GitHub, até breve! 🚀