Revisitando alguns conceitos de DDD
Você já deve ter ouvido falar de DDD (Domain Driven Design), ou até mesmo use alguns de seus conceitos no seu dia dia. Mas será que está utilizando bem? Bom, eu me fiz esta pergunta e decidi revisitar alguns pontos importantes. E como quase tudo que aprendemos e revisitamos um tempo depois, percebi que estava cometendo alguns erros, seja por que algum conceito não estava tão claro como eu imaginava, ou por desconhecimento de algumas questões.
Para compartilhar um pouco do que reaprendi, resolvi escrever este artigo repassando pelos principais conceitos de DDD com algumas dicas que espero que sejam úteis para você!
Saiba quando usar
Um erro muito comum quando estamos começando é de querer usar todos conceitos em tudo que vemos. Será mesmo que precisamos usar DDD em toda aplicação que vamos construir?
Para responder essa pergunta, temos que ter em mente que o propósito do DDD é facilitar a modelagem um domínio de negócios. É uma ferramenta para atacar a complexidade, e não para gerar mais dificuldades. Nem todo serviço que construimos vai se beneficiar da sua implementação. Portanto, antes de implementar, é bom se perguntar:
"DDD vai me ajudar a organizar e descomplicar a modelagem do serviço, ou vai simplesmente adicionar mais regras e complexidades desnecessárias?"
Linguagem Ubíqua
Para que possamos discutir e modelar o domínio da melhor forma, é preciso o cultivo de uma linguagem compartilhada e voltada para o domínio do negócio: a linguagem ubíqua. Não se trata apenas de usar os nomes corretos no código e nas reuniões. É mais que isso. É sobre o código expressar como o negócio se comporta na realidade. E para isso, precisamos estar alinhados com as pessoas que mais entendem sobre o domínio que vamos modelar.
Como programadores, temos uma tendência a simplificar as coisas, e nem sempre será a melhor forma de representar o negócio. Por exemplo, ao invés de simplesmente usarmos um setter para definir o preço de um produto, por que não utilizarmos métodos que expressem o motivo da mudança?
Começando a modelagem
Vamos começar recapitulando alguns dos problemas que o DDD se propõe a resolver. Para isso, é comum ser adotado o diagrama de "Espaço de Problema vs Espaço de Solução". Do lado esquerdo, temos a o que precisa ser resolvido, e do lado direito, a solução que DDD nos provê:
Apesar de simplificado, esse diagrama é bem útil para nos familiarizarmos com alguns termos. O modelo de domínio é como representamos o domínio do negócio. Dentro de nosso domínio há divisões, os subdomínios, que geram os contextos delimitados do lado da solução. Vamos entender melhor o que isso tudo significa. Para fins didáticos, vamos supor que estamos trabalhando com o domínio de uma loja.
Bounded Contexts (Contextos Delimitados)
Sabemos que uma loja possuí diversas áreas (departamentos). Essas divisões internas é o que chamamos de subdomínios. Tomemos alguns como exemplo:
Dentre os subdomínios, podemos ter entidades que representam coisas distintas, mas que possuem o mesmo nome. Podemos ter também entidades que representam a mesma coisa, mas com nomes e modelagens diferentes. Por exemplo, um “Cliente” no contexto da “Loja online” é diferente do “Cliente” no contexto de “Marketing” ou "Estoque". Isso parece ser um problema para nossa linguagem ubíqua, não é mesmo?
E é ai que entra o contexto delimitado. Ele é um espaço em nosso serviço, de fronteira bem definida, no qual nossas entidades não sofrem com ambiguidades. O contexto delimitado permite que nossa linguagem ubíqua continue consistente. Dentro dele, cada entidade pode adotar o nome e a modelagem da forma que a melhor representa. Assim, "Cliente" pode representar o fornecedor no contexto de "Estoque", e um consumidor no contexto de "Loja online" sem que hajam conflitos.
Um contexto delimitado define limites para que os serviços não tenham modelos conflitantes dentre deles. Se um microsserviço cruzar a fronteira de um contexto delimitado, é provável que o conflito nos traga problemas. Assim, um contexto delimitado pode abranger um ou mais microsserviços. Porém, é altamente improvável que um contexto delimitado seja contido dentro de um único microsserviço, pois neste caso teríamos um micro-monolito.
Entities e Value Objects (Entidades e Valores de Objeto)
Com nossos contextos bem delimitados, é hora de construir. Os bloquinhos do nosso domínio são as entidades e os valores de objeto. Mas qual a diferença entre eles?
Entidades
Para determinarmos o que é uma entidade, vamos olhar para algumas de suas principais características:
- Toda entidade deve possuir um id no domínio e deve ser identificada por ele.
- Uma entidade é mutável. Isso significa que seus valores podem mudar e a entidade continuará sendo a mesma
- É o id que determina se duas entidades representam um mesmo objeto
Por exemplo: olhemos para a classe "Cliente" de nossa loja online. Se um de seus atributos mudar (digamos, o endereço), ainda estamos tratando do mesmo cliente? Sim! Portanto, um cliente deve ser identificado pelo id, e não pelo valor de seus atributos. Assim, cliente é uma entidade em nosso domínio.
Entidades não devem ser classes anêmicas (simples estruturas de dados). Como parte fundamental da modelagem do domínio, elas devem expressar comportamentos e encapsular regras de negócio com a ajuda da linguagem ubíqua. Lembre-se que a linguagem ubíqua não nos diz somente como as coisas devem se chamar, mas sim como elas são expressas!
Valores de Objeto
Valores de objeto, como o próprio nome diz, servem para agregar informações (ou valores) a uma entidade. Ao contrário das entidades, não devem ser identifcados por um id, mas sim pelos valores de seus atributos. Isso significa que se algum valor mudar, não estamos falando mais do mesmo objeto.
Tomemos como exemplo uma classe "Endereço". Se mudarmos o valor do atributo "bairro", estamos falando do mesmo endereço? Não! Value Objects são imutáveis. Logo, "Endereço" deve ser um value object em nosso domínio.
Value objects também não precisam ser anêmicos. Uma classe "Cpf", por exemplo, poderia conferir se o número informado está de acordo com o que se espera de um cpf válido.
Agreggates (Agregados)
Para entender o que são e como chegar nos Agregados, precisamos primeiro dar um passo para trás para entender como nossas entidades se relacionam. Sabemos que para ter um relacionamento, elas precisam estar conectadas de alguma forma. Podemos fazer isso referenciando o id da outra, ou através composição (uma pertencer à outra). Mas quando usar cada um? Tomemos como exemplo algumas entidades envolvidas num processo de um Pedido em nossa loja:
Nosso pedido deve possuir um endereço para entrega e também conter os itens que foram pedidos. Podemos estabelecer que há uma relação de composição de "Pedido" com "Item" e com "Endereço" pois as duas últimas dependem da existência do pedido para existir. Elas pertencem ao pedido. Mas e quanto ao "Cliente"? Nosso cliente, apesar de se relacionar com o pedido, não precisa dele para existir. Neste caso, a melhor saída para estabelecer o relacionamento é utilizando uma referência do "Cliente" dentro do "Pedido". Agora temos nossa modelagem melhor esboçada:
Neste processo de estabelecer quais entidades possuem uma relação de composição estamos formando os agregados. Um agregado se trata de um grupo de entidades e valores de objetos que possuem forte dependência:
Em termos mais genéricos, temos algumas heurísticas para nos ajudar a formar agregados mais coesos. Dadas duas entidades X e Y, é recomendado que elas estejam no mesmo agregado quando alguma das seguintes condições ocorrer:
- se X mudar Y também será alterado
- mudanças em X dependerem de Y para acontecer
- operações que envolvem X e Y estão dentro de uma mesma transaction
Agora que já sabemos melhor como estabelecer nossos agregados, precisamos estabelecer qual será sua raiz. A raiz é a entidade mais importante do agregado, e que também dá nome a ele. As outras entidades do agregado perdem seu valor se a raiz deixar de existir. No nosso exemplo, "Cliente" é a raiz do seu próprio agregado (afinal, só existe ele), e "Pedido" é raiz do outro agregado (sem um pedido, endereço e os itens pedidos perdem sentido).
As raizes também são a porta de entrada, no nosso domínio e no mundo externo, para que as outras entidades do agregado sejam acessadas. Em outras palavras, as entidades de um agregado só devem ser alcançadas através da raiz. Por exemplo, se precisássemos de um endpoint para pegar os itens de um pedido, deveríamos ter algo como:
GET pedidos/{id_pedido}/itens
É muito importante que saibamos identificar os agregados e as raizes de nosso domínio, pois estes serão chaves para outros conceitos de DDD, como repositórios e eventos.
Eventos de domínio
Sabemos que entidades são classes mutáveis. Pode ser de interesse de outros serviços ou de outros subdomínios saber quando uma alteração aconteceu.
Uma boa prática é emitirmos os eventos de domínio por agregados. Assim, quando uma mudança ocorrer em alguma entidade, notificamos o ocorrido junto de todo agregado. Isso é importante tanto para garantimos que as informações que precisarão ser consultadas estejam lá (não queremos obrigar ninguém a fazer chamadas pra buscar dados), quanto para que possamos garantir a ordem dos eventos em relação ao nosso agregado.
Recapitulando:
- Utilize linguagem ubíqua para expressar seu domínio
- Estabeleça uma zona segura para a linguagem ubíqua: o contexto delimitado
- Estabeleça as entidades e os valores de objeto do seu domínio. Entidades são identificadas por ids, valores de objeto pelos atributos
- Determine a relação entre as entidades para formar os agregados
- Entidades dependentes entre si devem estar no mesmo agregado. A entidade mais importante é a sua raiz
- Emitimos um evento de domínio sempre que uma entidade mudar. É uma boa prática enviar o agregado como um todo
Lembrando que estes conceitos não estão escritos em pedra, é natural que hajam visões diferentes sobre os mesmos.
Até a próxima!