DEV Community

Pedro Mafra
Pedro Mafra

Posted on

Domain-Driven Design: como evitar o "Big Ball of Mud"

Introdução

O DDD acabou virando algo meio místico nos dias de hoje, em que muito se fala e nada se entende. Muitos acham que é apenas uma série de Design Patterns, ou até mesmo codar de forma estruturada em pastas. Este artigo buscará mostrar que o DDD vai muito além disso.

De início, podemos entender a sua origem: o DDD foi um conceito abordado no livro de Eric Evans lançado em 2003 que, desde então, vem sendo amplamente utilizado principalmente ao se trabalhar com grandes projetos, objetivando uma clareza mais profunda do produto e possibilitando que o software sobreviva de forma saudável.

Sendo assim, fica a pergunta: mas o que esse conceito apresenta de tão especial?
Bom, para entender isso, vamos tentar dar defini-lo:


“Domain Driven Design é modelar um software com base em seus diferentes domínios, buscando entender suas regras, processos e complexidades. É basicamente moldar problemas e trazer soluções a eles, separando as responsabilidades de cada um.”


Podemos entender o DDD quase como uma filosofia, uma forma de pensar ao se trabalhar com software. Hoje em dia, em que se trabalhar com microsserviços tornou-se tão importante, essa filosofia aumenta ainda mais seu protagonismo → grande parte do desafio dos MS é exatamente conseguir modelar o software, separar suas complexidades e entender seus contextos para conseguirmos criá-los de forma mais independente.

Além disso, em um âmbito mais prático, também apresenta princípios base e Patterns para que o dev consiga trabalhar em projetos com diferentes escopos de forma mais decente e organizada.

Algumas considerações

Antes de seguir no assunto, vamos fazer e refletir com algumas considerações:

  • DDD é para projetos mais complexos;

O negócio e o domínio devem ter certo nível de complexidade para que consigamos modelá-lo. Não adianta querermos usar DDD p/ modelar um software super simples com 5 cruds - o DDD é utilizado quando temos problemas maiores e não temos clareza do todo, como uma coisa se relaciona com a outra, e também quando temos muitas pessoas e de departamentos diferentes envolvidos;

  • Projetos complexos possuem muitas áreas, regras de negócio, pessoas com visões diferentes em variados contextos;

Considere o exemplo:

Em uma certa empresa A, mais “old school”, o coração do negócio (que traz o resultado esperado) pode ser fazer vendas ligando para os clientes um a um, e chama este processo de “vender por live-call”. Já em outra empresa B, mais moderna e automatizada vendendo através de seu site, as ligações 1 a 1 servem apenas para suportar/complementar o negócio base (de vender online), e chamam esse processo de “vender no one-on-one”.

Podemos ver que são duas coisas “iguais” em contextos diferentes, e portanto com significados e graus diferentes de importância. Cada empresa também terá sua forma específica de se expressar e seus jargões específicos.
O software é vivo e movido a pessoas e contextos. Nós, devs, temos que entender isso e modelá-lo de acordo com estes princípios.

  • Não temos como tratar um software grande, complexo, em uma empresa com muitos processos e pessoas, sem utilizar técnicas avançadas;

  • Grande parte da complexidade do software não vem da tecnologia em si, mas sim da comunicação, separação de contextos, entendimento do negócio por diversos ângulos;

Muitas vezes quando um software começa a dar problema não é por conta da tecnologia escolhida, e sim por conta da complexidade de negócio. Quando vamos “transformar” o negócio para tecnologia, se não tivermos clareza do mesmo, é quase certo que o software terá problemas no futuro. E uma das principais formas de ter essa clareza é usando o DDD.

  • Em uma empresa temos devs, POs, gerentes, diretoria, etc., pessoas com várias opiniões distintas;

Não podemos ser inocentes de achar que o software tem uma visão única - temos que entendê-lo com base no contexto de cada um e ter técnicas adequadas para extrair a informação e entregar o que realmente precisamos.

Como o DDD pode ajudar

  • Vai nos ajudar a entender com profundidade o domínio e subdomínios da aplicação;

  • DDD diz para termos uma linguagem universal (linguagem ubíqua) entre todos os envolvidos:

Um das coisas que geram muitos problemas é que diferentes times, contextos, colaboradores tem linguagens/jargões específicos. Isso aqui é um ponto muito importante pois permeia todo o processo de desenvolvimento → todos tem que estar na mesma página.

Exemplo: para um departamento de vendas, os ‘clientes’ vão ser uma coisa, para outro departamento, serão outra. Se ambos os departamentos começarem a falar juntos sobre ‘clientes’ pode haver confusão - vamos aprender a controlar isso no dia a dia com o DDD.

  • Vai ajudar a criar um design mais estratégico utilizando Bounded Contexts;

  • Vai ajudar a criar um design tático para conseguir mapear e agregar as entidades e objetos de valor da aplicação, bem como os eventos de domínio;

Com o design estratégico, conseguimos separar os contextos, com o tático, conseguimos mapear as entidades do negócio, para então começar a desenvolver o nosso programa.

  • Clareza do que é complexidade de negócio e complexidade técnica;

Por fim, deixo uma definição do Vernon sobre DDD:


“In short, DDD is primarily about modeling a Ubiquitous Language in an explicitly Bounded Context”


Resumindo, é conseguir modelar de forma explícita uma linguagem universal dentro de um contexto limitado do nosso domínio. Quando a linguagem muda, é sinal que já estamos em um contexto diferente de nossa aplicação.

Domínios, sub-domínios e contextos

Pense na seguinte situação: está num quarto escuro com uma lanterna. De cara, não é possível enxergar o quarto todo. Porém, se formos iluminando uma parte do quarto de cada vez, poderemos “juntar os pedaços” em nossa cabeça e ter uma noção de como o quarto é.

O mesmo acontece com o problema (domínio) que o software irá resolver. De cara, não temos noção do seu todo. Porém, se separarmos esse problema em problemas menores (subdomínios), poderemos juntar os pedaços para ter uma visão mais completa e criar uma solução.

Neste sentido temos as seguintes divisões: Core, Support e Generic, explicadas na imagem a seguir:

Image description

Um ponto importante é que os softwares auxiliares são substituíveis, enquanto o ‘core’ é o que dá sentido a nossa aplicação - sem ele nosso projeto não existe.

Podemos entender essa separação em domínios e subdomínios como a definição de nosso Problema. Em sequência, o que faremos é o molde da Solução. Neste sentido, a partir dos subdomínios observados iremos delimitar contextos a eles → cada contexto provavelmente acabará por virar subprodutos a serem desenvolvidos por nós, devs.

Image description

Em resumo, vemos o problema (domínio), o quebramos em subproblemas (subdomínios), e então criamos fronteiras explícitas para esses problemas menores (contextos) que farão parte de nossa solução. Mas como faremos essa delimitação?
Bom, vamos analisar outra citação do Vernon:


“A Bounded Context is an explicit boundary within which a domain model exists. Inside the boundary all terms and phrases of the Ubiquitous Language have specific meaning, and the model reflects the Language with exactness.”


Com isso, podemos entender que uma das formas de definir essa fronteira é o uso da linguagem universal. Tudo que é específico daquele negócio, como as pessoas se comunicam e como os problemas são resolvidos possui uma terminologia → nesse caso geralmente todos estão dentro do mesmo contexto. Quando o linguajar começa a mudar, é indício que estamos cruzando a fronteira e entrando em um outro contexto. De forma simples, podemos identificar contextos diferentes quando:

  • Temos palavras iguais significando coisas diferentes → um “ticket” no Contexto de “Venda de Ingresso” é diferente de um “ticket” no Contexto de “Suporte ao Cliente”;

  • Temos palavras diferentes mas que significam a mesma coisa → ”cliente”, “freguês”, “usuário”, “comprador”;

Agora ficou ainda mais claro do porque não conseguimos utilizar o DDD em sistemas simplórios → o contexto é tão pequeno que todo mundo já “fala a mesma língua”, sendo assim não tem tanto sentido fazer uma modelagem de domínio.

Elementos transversais

Entendendo a importância dos Bounded Contexts, que irão determinar qual área da empresa estamos trabalhando e o problema que estamos resolvendo, podemos também entender que contextos diferentes muitas vezes acabam “conversando” entre si → poderemos ter elementos transversais, que estão em contextos diferentes porém com perspectivas diferentes.

Image description

Aqui, o cliente é o mesmo, mas estão em contextos diferentes. Logo, para cada contexto teremos preocupações diversas (que poderão ser as propriedades de uma entidade Cliente), e isso é uma das coisas que mais gera confusão para os devs. O que mais acontece nos dias de hoje é modelar um Cliente tentando atender as preocupações de diversos contextos diferentes → nossa entidade/classe Cliente acaba ficando enorme e confusa.

Se tenho contextos diferentes, mesmo que a entidade seja a mesma, tenho que modelar ela de acordo com o contexto. Sendo assim, se estou em um monolito, e tenho uma área de suporte e outra de ingresso, ainda sim terei que criar entidades diferentes para representar esse Cliente.

Sem toda essa delimitação a aplicação vai ficando um monstro. Se quisermos depois quebrá-la em microsserviços, por exemplo, teríamos bastante esforço de reescrita.

Context Mapping

Agora vamos pra um lado mais prático. Temos que ter estratégias para trabalhar e organizar as comunicações entre os contextos e também entre os times da empresa para iniciar o desenvolvimento - o Context Mapping descreve exatamente as possíveis perspectivas para termos uma visão holística sobre essas relações. Neste sentido, temos alguns padrões a se utilizar:

Partnership (parceria)

Contextos podem compartilhar e consumir apis entre si. Exemplo: contexto de venda de ingressos online e contexto de vendas de ingressos a partir de parceiros.

Shared kernel

Estes sistemas parceiros muitas vezes também podem ter um shared kernel. Exemplo: criar um sdk para ambos utilizarem.

Customer-Supplier development - upstream/downstream

Um contexto vai fornecer um serviço para o outro consumir e poder realizar seu negócio, ditando as regras dele. Exemplo: a venda de ingressos online (downstream) que consome um serviço do contexto de pagamentos (upstream).

Conformist

Considerando que o contexto de pagamentos consuma uma gateway externa de cartão de crédito, ele terá que se conformar com as regras do serviço disponibilizado (a não ser que o cliente corresponda a uma grande parte dos lucros do fornecedor, nesse caso poderá ter algum poder de decisão).

Anti-corruption layer

Quanto mais conformista a relação, mais vou acoplando os sistemas e ficando difícil de substituir. Logo, para evitar isso, podemos criar uma ACL - camada de interface que servirá como um adaptador entre nosso contexto e a gateway utilizada - irá minimizar o problema de conformidade.

Open Host Service

Um serviço (upstream) fornece um protocolo compartilhado que dá acesso aos seus subsistemas, como API Rest ou gRPC.

Published language

A tradução entre os modelos de dois contextos delimitados precisa ter uma linguagem comum, bem documentada para expressar as informações de domínio necessárias. A “linguagem publicada” é frequentemente combinada com um serviço de host aberto (open host service).

Separate ways

Quando contextos não tem relações de comunicação, desenvolvedores podem encontrar soluções simples e especializadas dentro de cada escopo específico.

Big Ball of Mud

Quando um sistema tem modelos misturados e limites inconsistentes - não podemos permitir que essa “bagunça” se propague para outros contextos delimitados. É uma demarcação da baixa qualidade de um modelo/sistema.

Image description

https://github.com/ddd-crew/context-mapping?tab=readme-ov-file#context-mapping

Top comments (0)