Uma breve visão sobre Domain-Driven Design
Desenvolvimento Guiado pelo Domínio
Eric Evans, em 2003 lançou um livro que introduziu termos fundamentais na arquitetura de software e que hoje, fazem parte do vocabulário de vários arquitetos, engenheiros e desenvolvedores. Eu chamo de “termos fundamentais”, porque o que Eric Evans fez na minha opinião, foi catalogar com muita consciência, boas práticas que os melhores desenvolvedores até então já praticavam no dia-a-dia mas não conseguiam transmitir com clareza a relação de todas essas boas práticas em um conjunto.
Eric Evans sistematizou todos esses conceitos de uma forma mais fácil de se explicar e de se entender e criou o que eu chamaria de “Guia Definitivo de Modelagem de Domínio do Problema”.
A metodologia que apoia esse ecossistema foi nomeada por ele como Domain-Driven Design — Desenvolvimento Guiado pelo Domínio. E o motivo desse nome é que essa abordagem se baseia na identificação dos elementos do Domínio do Problema, sendo que tudo que não fizer parte desse domínio só existirá se for para dar algum suporte ao elementos desse domínio. Desse modo conseguimos focar na complexidade mínima necessária para gerar um software que atenda os requisitos e tanto quanto possível, com menos complexidade acidental.
Quando falamos em Domínio, ou Domínio do Problema, entenda como um “assunto”, um limite imaginário que define o que faz parte ou não do assunto, quais palavras ou termos, quais atividades, quais leis ou regras, tudo que envolve o assunto tratado como problema a ser resolvido pela solução de software.
De fato isso tem sido uma boa prática há anos. Simplesmente estamos dizendo que se o Domínio do Problema não estiver claro o suficiente, qualquer solução implementada poderá não ser satisfatória, ou em outras palavras, se começarmos a desenvolver uma solução antes de entendermos a composição do problema, estamos escrevendo um livro sem termos um assunto.
Quando o autor enfatiza no título do seu livro que através de DDD é possível “atacar a complexidade no coração do software” é sobre isso que ele está falando. Mirar no núcleo do software, onde a complexidade natural do problema está, e garantir o maior entendimento possível sobre o problema que estamos atacando para assim não criar novos.
Então, é sobre o que podemos fazer para modelar esse domínio que vou tentar aprofundar nos tópicos seguintes.
O Domínio
Na Engenharia de Software, Domínio refere-se praticamente ao Domínio do Problema, que podemos aqui comparar com um mapa que delimita o conjunto de coisas que fazem parte do contexto que estamos concentrados em entender e resolver.
Começar a projetar um sistema começando pelo Domínio, parece bastante óbvio, apesar de na prática as vezes não ser.
A cultura e a experiência de cada pessoa influencia na forma como ela se orienta durante a solução de um problema de software. Seja começando por storyboards desenhando as tela que o sistema precisa ter, ou colando post-it num quadro, contendo as funcionalidade que o sistema deve ter através de User Stories.
Por outro lado, é natural para vários desenvolvedores começar um sistema pelo bom e velho MER (Modelo de Entidade e Relacionamento). Durante um bate-papo, quando a ideia de um sistema começa a nascer, quase sempre emerge num espaço em branco diagramas de quadrinhos interligados. Isso porquê nessa fase inicial, o que estamos tentando fazer é entender o contexto do problema do jeito mais fácil que é criando um mapa.
O mapa é um facilitador, enquanto representa com certa fidelidade elementos pertinentes ao problema dentro de uma fronteira, através de uma imagem. A imagem é importante pois, cria uma forma visual explícita de quais são, onde estão e a importância dos elementos dentro do mapa. A alternativa a isso seria a imaginação, ou seja, criar essa imagem na cabeça, precisando explicar repetidamente em conversas e/ou correr o risco de ignorar elementos importantes por simples esquecimento.
Todo mapa é composto por diversos tipos de elementos e isso também é verdade num modelo de domínio, ele inclui objetos concretos ou abstratos, como pessoas, leis, regras, termos, linguagem e dicionários, métodos, convenções, princípios e padrões, características, funções e relacionamentos, etc.
Todos esses objetos podem classificados como Entidades, Objetos de Valor, Agregados, Repositórios, Serviços, etc e serem estereotipados como Blocos de Construção, pois, essas peças ao se juntarem criarão uma solução firme para o problema.
Building Blocks
Para construir uma solução firme precisamos representar nosso modelo com vários tipos de objetos. Principalmente em softwares corporativos, o tempo nos mostrou que esses objetos quase sempre podem ser categorizados como:
Entidade
São coisas representadas pelas suas características, e essas características podem ser alteradas com o tempo, sendo que mesmo suas características mudem, elas continuam sendo as mesmas coisas.
Mas se todas as características forem alteradas, como sabemos que é a mesma coisa? Precisamos de uma identidade, um ID.
Coisas que se encaixam nessa classificação: pessoas, empresas, carros, produtos, etc. Pessoas mesmo que gêmeas possuem um CPF diferente. Dois carros com as mesmas peças, acessórios e cor possuem chassi diferente.
Objetos de Valor
Não mudam. A importância dele está na composição das suas características (valores), os novos são importantes e os anteriores podem ser descartados.
Normalmente são tipos complexos demais para serem modelados como tipos primitivos, pois possuem mais informações a serem extraídas, e se não forem tratados como objetos de valor, acabam gerando vazamento de regras de negócio para outras camadas.
Coisas nessa classificação não precisam de uma identidade. Por exemplo um panfleto, um cartão de visitas, um cartão de crédito, um número de telefone, um RG ou CNH.
Repositórios
Uma forma de persistência de informações, um conjunto não volátil de entidades.
No mundo real é um guarda-roupas, um disco rígido, uma mochila, enquanto as entidade seriam as roupas, os arquivos ou os cadernos, respectivamente.
Serviços
A lógica de execução dos comportamentos do sistema. Execução do fluxo de trabalho a aplicação das regras básicas do negócio ou de regras que existem para que o negócio funcione enquanto sistema automatizado.
É aqui que está a semântica do sistema, ou seja, o que ele faz com os dados que possui.
Casos de uso podem ser bons exemplos de serviços. Eles são metas do usuário dentro do sistema, recebem entidades como premissas, processam fluxos básicos e alternativos respeitando as regras do negócio, eventualmente persistindo dados ou acionando sistemas externos e como resultados produzem outras entidades.
Tecnicamente falando, ao vermos um caso de uso “Solicitar Pagamento de Fatura” podemos implementá-lo como um serviço de domínio. Provavelmente ele estará dentro de um agregado de Faturas, num domínio de Pagamentos, podendo receber uma entidade Fatura, validar internamente as propriedades da Fatura, conferir o saldo do cliente e comunicar-se externamente com um Gateway de pagamentos, persistir o que for necessário ser guardado num banco de dados para que alguém consulte posteriormente e devolver uma outra entidade chamada Recibo como resultado para o Ator que estimulou esse caso de uso.
A forma como esse serviço fará esse fluxo ser bom para a empresa é que consideramos as regras de negócio. As regras de negócio impedem que o fluxo gere prejuízo e/ou também para que o sistema tome a melhor decisão naquele momento para gerar maior rendimento para a empresa.
Por exemplo, a regra de negócio pode definir que o caso de uso acima decida entre diferentes gateways de pagamento que naquele dia ou horário tenha menor cobrança de taxas.
Model-Driven Design
Uma das metodologias já existente e que Eric Evans incluiu no DDD foi a Model-Driven Design, que sugere que seu sistema comece a ser desenvolvido pelo Modelo de Domínio.
O Modelo de Domínio, nada mais é do que um Mapa. Um conjunto coeso de entidades, relacionamentos, papeis e eventos necessários para representar a realidade segundo a visão do seu modelador.
Isso basicamente significa que você começa a desenvolver seu sistema a partir da modelagem das entidades do negócio, o que chamamos de Domínio do Problema. A partir de uma modelagem bem estruturada e rica, todo o resto só existirá se houver algum propósito em dar suporte tecnológico à esse domínio (camadas de acesso a dados e serviços externos, que em DDD fazem parte de uma camada superior chamada de Infraestrutura) ou prover usabilidade para o domínio (camadas de aplicação e apresentação). Note na imagem acima, que todas as setas partem da elipse denominada Model-Driven, entretanto nenhuma outra aponta para ela, ela é independente — falaremos disso em algum outro momento.
DDD fala muito de como você deveria focar em destilar o seu domínio. Extrair requisitos, modelar e estabelecer uma comunicação com experts do negócio. Em algum momento enquanto você tenta fazer isso, perceberá que para ser eficiente será necessário ir além, precisará de uma linguagem comum, precisará delimitar contextos e definir como eles se relacionam.
Contextos Delimitados
Um bounded context explicado de forma simples, representa um sub sistema dentro de todo o seu sistema.
Imagine num cenário onde seu sistema é muito complexo, cobrindo quase todas áreas ou tarefas de uma empresa, você vai perceber que o Modelo de Domínio se tornou extremamente complicado, com centenas de entidades e relacionamentos. Isso é um possível sinal de que você deve classificar essas entidades baseando na feature ou área que elas atendem, ou qualquer outra forma, e dividi-las em subconjuntos chamados em DDD de Bounded Contexts.
Cada Bounded Contexts terá seu próprio Domínio e a separação de camadas que lhe for conveniente.
Importante, não se preocupar em como ficará o banco de dados, ao fazer isso, mas adianto que é bom que o banco de dados sejam separados também! Chegaremos lá!
Layered Architecture
É muito mais fácil resolver um problema grande, segmentando-o e isolando-o em problemas menores.
Se considerarmos o princípio de Pareto para o cenário de desenvolvimento de software, podemos dizer que 80% da complexidade está em 20% do código. 1/5 do sistema pode conter 80% da complexidade. Pode ser exagero, mas de certa forma isso faz sentido, talvez na prática pode haver variações (70/30, 90/10) mas o princípio será o mesmo.
Vamos então pegar todo esse problemão e dividi-lo em partes. Para ajudar fixar o conceito, passarei a chamar essas partes apenas de camadas.
Uma camada representa um pedaço coeso do seu sistema, com responsabilidades bem definidas e menos dependente possível do resto do sistema, porém, ligado a alguma outra parte para colaborar com o propósito principal do sistema. Esse conceito é antigo na ciência da computação e é chamado de SoC — Separation of Concerns.
A quantidade de camadas que você precisará para sua aplicação você mesmo irá definir, não sendo obrigatório ter um modelo de 3 ou 4 camadas. A arquitetura proposta pelo time da Microsoft, por exemplo, eles o sugerem como sendo N-Camadas.
Como DDD sugere a separação em Camadas?
O DDD propõem uma separação normalmente adotada de 4 camadas, Domain, Infrastructure, Application e Interface.
As camadas podem ou não ser separadas fisicamente ou logicamente, podem ser declaradas de forma explícita ou implícita. Ou seja, para estar em DDD você não é obrigado a criar um componente (.dll, .exe, .jar, .war, etc) para cada camada. Também não é obrigado a dar um nome padrão para as camadas. O importante nesse conceito é o desacoplamento e é criar um subconjunto coeso. A forma como vai fazer pode ser guiada pelo tamanho do seu sistema. Em projetos pequenos uma camada pode ser representada em uma Classe e em projetos maiores uma camada pode ser representada por um servidor (Tier). Enfim, não se apegue ao fato de criar um template para isso, normalmente existem soluções diferentes para problemas diferentes.
Domain Model
Em uma camada colocamos tudo o que for classes ou entidades do nosso Diagrama de Entidade-Relacionamento, requisitos funcionais e regras de negócio, atores, etc. Podemos chama-la de Domínio porque aqui é onde estamos modelando a solução e tudo o que está no Domínio do Problema do nosso sistema. Devemos tentar ser tecnologicamente neutros nessa etapa, atender os requisitos sem preocupar com a tecnologia que será usada, pensar em como refletir como a solução existiria num mundo real se não houvessem outros softwares, substituindo qualquer necessidade de tecnologia por abstrações/interfaces. Por exemplo, não fale Banco de Dados de Refeições, fale cardápio. Use a linguagem do seu cliente, isso irá reduzir problemas de comunicação e permitirá tanto a você quanto ao seu cliente aprenderem durante o processo de desenvolvimento do negócio, levando assim a um domínio mais rico — esse conceito é chamado de linguagem ubíqua. Agora que já sabemos como esse modelo se sustenta dentro do domínio. O resto do desenvolvimento será guiado por esta camada.
Infrastructure
Vamos criar uma camada que irá interpretar as abstrações do domínio e agora sim irá implementar soluções de tecnologia para elas. Se o domínio pedia um objeto que possuísse a habilidade de guardar uma pilha de pedidos, então é nessa camada que nos preocuparemos em como fazer isso. Conectar ao banco de dados ou ao FTP ou sistema de arquivos, isso é um problema de tecnologia, de fornecer infraestrutura, para nossa solução, por isso chamaremos dessa forma essa camada, “Infraestrutura”.
Application
Crie uma camada que ligue a solução tecnológica ao domínio do seu negócio. E nesse ponto, nosso sistema já está funcionando, as regras já são válidas, sua infraestrutura fornece suporte suficiente para o core business fluir. Essa camada oferece as regras de negócio que não são exclusivas do domínio, e sim sobre a orquestração do fluxo, a ideia é fornecer uma interface simplificada para tornar a solução consumível pela tela ou por outros sistemas. Já temos toda uma definição de aplicação, e chamaremos assim essa camada.
Interfaces
A última parte, torna nossa aplicação amigável ao usuário final, nessa parte nos preocupamos com a forma como o usuário interage com o sistema e como melhorar sua experiência. Essa parte tem o papel resumido de fornecer a interface do usuário com a Aplicação, e por isso essa camada se chama Interfaces. Ela fornece um ponto de interação tanto IHM (interface humano-máquina) quanto M2M (Machine To Machine).
Exemplo são as GUI (interfaces gráficas como uma tela de um aplicativo ou uma página web) que são projetadas para serem utilizadas por pessoas enquanto API (interface de aplicativos para interação via programação) onde normalmente um sistema interage com outro sistema através de referência de bibliotecas ou protocolos, como ocorre com webApi.
Linguagem Ubíqua
A linguagem ubíqua preza pela comunicação das pessoas envolvidas no sistema. Ela sugere uso de termos oficiais e não ambíguos para garantir que todos os envolvidos estejam falando e entendendo a mesma coisa.
A introdução de termos tecnológicos em um domínio que não é sobre tecnologia ou uma adoção de nomenclatura de forma automática, pode prejudicar essa comunicação.
Cross Cutting
Cross-Cutting é um termo usado para referenciar recursos reaproveitáveis independente da camada que o utiliza (transversais). São normalmente abstrações (classes abstratas ou interfaces), configurações ou pequenas funções que praticamente nunca mudam ou são abrangentes demais para estarem dentro de uma camada de domínio ou infraestrutura.
Alguns estilos arquiteturais sugerem a criação de um componente nomeado como Cross Cutting que suportam todas as camadas. No próprio diagrama de arquitetura proposta pela Microsoft que vimos anteriormente, essa camada é incluída.
Vale dizer que Cross Cutting é como um estereótipo, ou seja, você pode ter vários componentes que são do tipo Cross Cutting.
Se pensando em DDD vale sempre analisar com atenção se essas responsabilidades transversais não deveriam ser um Contexto a parte.
Shared Kernel
O termo Shared Kernel se refere a forma como os contextos tratam a dependência que eles tem de recursos transversais. No caso, ambos os contextos podem/precisam contribuir ativamente para sua evolução desse contexto transversal. Ambos dependem em seu núcleo de recursos que são comuns e compartilhados, essa forma de se referenciar essa dependência é chamada de Shared Kernel (núcleo compartilhado).
É comum pessoas confundirem um Bounded Context de Cross Cutting chamando-o de Shared Kernel, no entanto, SK se refere a um tipo de relação entre contextos e não um contexto. Numa analogia rápida, é como se confundir entre o que é Entidade e o que é Relacionamento.
Esse tipo de relacionamento é praticamente uma sociedade. Imagine uma situação onde duas pessoas possuem direito de usar algo comum a eles, algo que pode ser modificado, melhorado ou até quebrado. Uma alteração feita por um pode inutilizar ou causar um efeito colateral para o outro.
Esse tipo de BC trazem bastante benefícios se pensarmos em redução de redundância, porém, também possuem vários riscos como dificuldade de entender o impacto as alterações, rápida propagação de defeito.
Muitas das vezes os riscos fazem com que contextos compartilhados sejam menos adotados ou acabam fazendo com que seus dependentes criem adaptações do seu lado para evitar mexer no núcleo.
Dicas e notas
É natural ao aprendermos um coisa nova, tentar primeiramente correlacionar com algo que já conhecemos. Isso tem causado uma grande confusão sobre DDD. Alguns trazem conceitos de arquiteturas BOLOVO por exemplo, e isso torna a curva de aprendizado mais complicada. Minha dica é tentar manter a mente aberta para uma forma totalmente nova de fazer sistema, depois que já tiver entendido e aplicado na prática algumas vezes, você vai conseguir correlacionar as coisas que realmente forem relevantes.
O importante ao começar conhecer DDD é entender que ele é um catálogo de boas práticas sistematizadas e voltadas para o Core Business de softwares, por isso, ele facilita a comunicação entre a Implementação e o Negócio, auxilia na estratégia de arquitetura e oferece uma baseline para a fase de Design.
Este foi apenas um resumo dos meus estudos e pontos de vista, baseadas em cursos, livros, bate-papo e implementações práticas. Meu propósito aqui é explicar alguns conceitos com uma linguagem equilibrada entre não tão técnico e mais técnico, principalmente em coisas que eu tive dificuldade em descobrir no início. No entanto, se como eu, você tiver interesse em se aprofundar no assunto, sugiro os livros do autor Eric Evants além dos matérias abaixo:
Ao longo de meu aprendizado, uma coisa que me incomodou muito foi a falta de exemplos práticos, então caso esse artigo seja bem recebido, espero estender o assunto publicando uma versão prática.
Pense nisso e, bom código!