No primeiro post desta série, contamos a história por trás da nossa decisão de implementar monolitos modulares. Nesta parte, vamos detalhar um pouco mais as características deles e o processo de descoberta dos módulos que utilizamos.

1 – Monolito e modular, as definições


     1.1 – Monolito Em uma tradução livre, de acordo com a Wikipedia, um monolito é uma aplicação autocontida e independente de outras. Como filosofia de design, esse tipo de aplicação é responsável não apenas por executar uma tarefa, mas todos os passos necessários para completar uma função particular. (https://en.wikipedia.org/wiki/Monolithic_application)   A imagem acima tenta ilustrar essa ideia, onde todos os componentes necessários para a execução de uma determinada tarefa estão contidos dentro das fronteiras da aplicação. Normalmente, um monolito pode estar associado a uma unidade de deploy.
     1.2 – Modular David Parnas escreveu, em 1971, um paper que, para nós, é a melhor referência sobre o que é um design modular e como construí-lo.  Nesse trabalho, Parnas define que um sistema modular é aquele que é dividido em um número relativamente independente de módulos, com interfaces de comunicação bem definidas. Cada um desses módulos é pequeno e simples o suficiente para que um programador consiga entender seu funcionamento como um todo. (On the criteria to be used in decomposing systems into modules, David Parns, 1971 – https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)
Projetar um sistema modular não é uma tarefa fácil: é necessário pensar em como os módulos podem ser separados; como estabilizar a comunicação entre eles; e como lidar com o acoplamento, o que pode tornar esse trabalho bem desafiador.
     
No diagrama acima, é possível ver como os módulos se comunicam por meio de interfaces bem definidas (as setas mostram a comunicação entre os módulos). A grande questão aqui é como identificar esses módulos.  A partir das definições de monolito e modular, podemos avançar e entender como podemos utilizar esses conceitos juntos, por meio dos monolitos modulares.

2 – Como identificar os módulos?

Muito se discute sobre qual seria a melhor forma de dividir uma aplicação em módulos. Pela natureza do problema que resolvemos aqui na Beep, achamos que a melhor solução seria utilizar alguma técnica que evidenciasse o problema de negócio. Escolhemos utilizar as técnicas de Domain Driven Design.
     2.1 – Bounded Contexts Na parte IV do livro Domain Driven Design, Eric Evans descreve o que ele chama de strategic design. É um conjunto de princípios que nos ajudam a guiar as decisões de design, para reduzir a interdependência entre as partes de um sistema. Um conceito fundamental descrito nessa parte são os Bounded Contexts. O autor se baseia no princípio de que, em um mesmo sistema, existem múltiplos modelos de domínio diferentes. Além disso, argumenta que é inviável tentar definir um modelo de domínio único para um sistema inteiro. Ele sugere, então, dividir esses domínios em torno da linguagem comum, falada pelos especialistas. Um Bounded Context delimita o significado da linguagem e a aplicabilidade de um modelo de domínio. (Domain Driven Design – Tackling Complexity in the Heart of Software – Chapter 14 – Maintaining the model integrity)
Normalmente, no início de um projeto, é comum – durante o processo de entendimento do problema – tentar modelar o problema como um todo, definindo, assim, um único esquema de banco de dados e um único modelo de objetos, que representam o domínio. A figura abaixo ilustra um caso desse:
   
Essa figura representa uma parte do modelo de domínio – ou melhor, o modelo de tabelas – de uma aplicação. Repare na quantidade de dependências que esses objetos têm. Um modelo único como esse é complicado de evoluir e mudar. A proposta do Evans é: observar como o problema de negócio é organizado em torno da Ubiquitous Language – a linguagem comum falada pelos especialistas de domínio – e que, a partir daí, se extraiam os Bounded Contexts.  

A figura acima mostra que, ao invés de ter o modelo inteiro, é possível dividi-lo em vários contextos, cada um com suas características específicas. Usando a Beep como exemplo, podemos destacar alguns dos contextos que temos trabalhado. Lembrando: a Beep é uma empresa de saúde, que permite que os usuários agendem exames ou vacinas, e nós vamos até as suas casas para atendê-los. Abaixo, uma amostra do que temos modelado:

A definição desses Bounded Contexts reproduz, de certa forma, como o negócio se organiza. Mas o mais importante é perceber que as fronteiras destacadas limitam o uso da linguagem e o vocabulário. Vamos entender isso melhor mais à frente.
     2.2 – O processo
Uma pergunta fundamental é como identificar os Bounded Contexts. Talvez o principal guia na identificação de Bounded Contexts sejam as diferenças entre a linguagem, aquela falada pelos especialistas do domínio, e suas diferenças entre partes distintas do sistema. Observar a linguagem falada nos permite entender melhor o significado de determinados termos dentro de uma comunicação específica. Por exemplo, aqui na Beep, o significado da palavra “paciente” pode variar dependendo de qual setor está envolvido na comunicação. Quando falamos sobre agendamento, algumas informações do paciente que estamos interessados são:
  • Qual é o nome e a data de nascimento?
  • Qual é o dia de agendamento?
  • Qual é o endereço de atendimento?

Quando estamos discutindo com a equipe de enfermagem sobre a vacinação de um paciente, estamos mais interessados no tipo de informação a seguir:
  • Como a caderneta de vacinação está organizada?
  • Qual é a próxima vacina a ser tomada?
  • Qual vacina foi aplicada?

E para terminar, em uma conversa com o Financeiro, algumas das informações de pacientes que eles estão interessados são:
  • Qual é o custo das vacinas aplicadas?
  • Foi usado algum desconto na compra?
  • Qual foi o meio de pagamento utilizado?
  • A nota fiscal foi emitida?

Esses são apenas três casos, nos quais a palavra “paciente” é usada com diferentes significados. Tentar representar todas essas características de paciente, em um único objeto, vai acabar gerando um modelo grande e complicado de adicionar mudanças. Observar a diferença entre os significados de determinado termo é uma dica importante da existência de contextos diferentes. No exemplo, poderíamos ter três candidatos que contextualizassem o uso do termo “paciente”: agendamento, enfermagem e financeiro.
Uma vez que temos esses candidatos, precisamos pensar em como podemos representar o termo “paciente” nesses contextos. De acordo com Domain Driven Design, não deveria haver traduções entre os termos que os especialistas de domínio usam e o código que está escrito. Isso significa que precisa existir algo que represente o conceito e seja nomeado como “paciente” no código. No nosso caso, como utilizamos uma linguagem orientada a objetos, provavelmente vamos ter uma classe no código para representar o paciente. Mas temos três contextos diferentes: o que fazer?
   
Na proposta acima, podemos notar que a classe “paciente”, nesse caso bem simplificada, é compartilhada pelos três contextos. Repare que, nessa mesma classe, tenta-se adicionar responsabilidades para satisfazer as necessidades dos três contextos. Esse tipo de solução acaba gerando a maioria dos problemas já listados, principalmente relacionados ao acoplamento e a dificuldade de suportar mudanças. A solução sugerida pelo Domain Driven Design é que cada contexto tenha sua própria implementação de “paciente”.
 
No diagrama acima, foi adicionada uma implementação de “paciente” para cada contexto. É importante notar que, além da classe “paciente” adicionada, cada contexto define a semântica de uso e as responsabilidades do paciente. Por exemplo: não faz sentido, no contexto de agendamento, ter uma implementação que verifique se a nota fiscal foi recebida. Da mesma forma que não é necessário saber sobre cadernetas de vacinação no contexto financeiro. A vantagem de uma separação como essa é que é possível adicionar mudanças sem impactos descontrolados pelo modelo inteiro.
     2.3 – Responsabilidades de cada contexto
Uma vez que os candidatos aos contextos foram identificados, é muito importante começar a definir as responsabilidades de cada um. Isso vai permitir enxergar as fronteiras entre eles e como se comunicam. No início desse processo de descoberta, sabemos pouco sobre o problema que estamos explorando. Logo, é imprescindível fazer isso de maneira iterativa, ou seja, começar com um modelo mais simples e ir adaptando à medida que o conhecimento do problema vai se solidificando.  Dessa forma, observar a linguagem vai lhe ajudar a refinar os contextos e suas responsabilidades. No exemplo que vimos até agora, só nos atentamos ao uso do termo “paciente”. Mas quais seriam as responsabilidades do contexto de agendamento? A resposta simples e direta poderia ser algo como: 
“Permitir que o paciente registre um agendamento para uma data e um horário.” 
Com essa definição, aparecem outros termos que, de alguma forma, devem estar explícitos dentro do contexto de agendamento: Agendamento, Data e Horário. De acordo com o Domain Driven Design, deveríamos conversar diretamente com os especialistas do domínio e modelar diretamente da linguagem usada por ele. Nesse caso, aqui na Beep, se formos falar com algum especialista de domínio sobre agendamento, ele poderia descrever essa mesma frase desta forma:
“Permitir que um cliente registre um agendamento em um slot disponível para ele ou algum dependente.” As duas frases têm diferenças importantes: a primeira mostra que o especialista (nesse caso, a Central de Relacionamento) não usa “paciente” diretamente para falar de agendamento; na verdade, quem agenda é um cliente. A segunda mostra que o especialista do domínio pode usar um vocabulário bem próprio do negócio e que, nesse caso, foi usada uma palavra diferente para referenciar data e horário: slot. Por fim, foi destacado que o cliente pode registrar o agendamento para um dependente.

Com esse refinamento, o contexto de agendamento já ganha novos elementos e uma correção no uso dos termos. Lembrando que aqui só estamos explorando a linguagem e o processo de descoberta dos Bounded Contexts.   Temos outro candidato que chamamos de “enfermagem”. Quais seriam as responsabilidades dele nesse contexto? Novamente, se não conversarmos com os especialistas de domínio, poderíamos chegar em uma definição simples:   “Registrar a aplicação de vacinas e coleta de amostras para exames de um paciente.”   Nesse caso, percebe-se que existem duas coisas diferentes envolvidas: vacinação e exames. Além disso, as necessidades são diferentes: para vacinação, a parte importante pode ser resumida da seguinte forma:    “Registrar a aplicação de vacinas em um paciente por uma técnica de vacina, com informação da dose aplicada, lote e data de validade das vacinas.” Nesse caso, temos novos termos e uma mudança significativa no nosso candidato a Bounded Context: não faz sentido um contexto com o nome “enfermagem”. De acordo com os especialistas de domínio, podemos chamar esse contexto de “vacinação”.

Além desse contexto, temos um novo candidato, ainda não explorado, mas provavelmente com grandes chances de se tornar um novo contexto, com suas próprias responsabilidades e modelo de domínio:

Nesse caso, é importante passar pelo processo de tentar conversar com os especialistas do domínio para entender melhor quais são as responsabilidades desse novo contexto. O nome correto é “exames”? Quais são os termos usados e como estão relacionados? Esses são exemplos de perguntas que podem lhe ajudar a observar a linguagem e como ela é usada.

3 – Onde estamos mesmo?


Este post tentou mostrar o processo de identificação e refinamento de módulos, usando uma abordagem baseada em Domain Driven Design, com ajuda de Bounded Contexts. O processo todo não é feito de uma vez. Muito provavelmente os primeiros desenhos e recortes dos Bounded Contexts vão ser simplistas e ingênuos. É preciso considerar que é um processo iterativo, que vai se refinando com o tempo. Então, estar aberto e preparado para mudanças é fundamental. Na próxima parte desta série, vamos explorar a modelagem de domínio e como podemos tirar vantagem de um modelo de domínio rico para expressar, de maneira clara, as nuances do domínio, e como podemos permitir acomodar mudanças de maneira mais fácil.

 

Até lá!