APIs e Microsserviços

25 mar, 2020

Domain-driven Design (DDD) em uma Arquitetura de Microsserviços

Publicidade

O processo de construção de componentes de software, implementados utilizando uma arquitetura de Microsserviços, não se trata apenas em nos preocuparmos com a estrutura de arquitetura e engenharia interna, aplicar as últimas tendências em tecnologia, utilizar padrões de design corporativos (Enterprise Design Patterns), desenhar camadas inerentes à infraestrutura da aplicação, estar ciente das complexidades presentes em sistemas distribuídos, etc.

Portanto, quando nos referimos aos nossos Microsserviços, a modelagem dos dados trata-se da etapa mais importante e também mais difícil durante todo o ciclo de vida deste processo. A etapa de design do modelo do domínio de dados apresenta alta complexidade tratando-se de uma tarefa crítica em direção ao sucesso do sistema sendo desenvolvido.

Certamente, modelar o núcleo de dados de nossa aplicação, atacando todos detalhes existentes ao desbravar o modelo de negócio, não se trata de uma tarefa trivial e para isso existem um conjunto de técnicas e práticas avançadas as quais visam nos auxiliar no processo de modelagem e qualificação do domínio de negócio, propondo-se entregar uma representação mais precisa de nosso modelo de dados bem como dos limites transacionais entorno destes.

Nos referimos a este conjunto de práticas de modelagem como Domain-driven Design (DDD), e neste artigo iremos explorar em detalhes alguns dos Design Patterns pertencentes ao DDD com o objetivo de entender e aperfiçoar estas técnicas do ponto de vista prático.

Domain-driven Design

Domain-driven Design (DDD), descrito em seu nível mais alto de abstração, refere-se basicamente à duas grandes camadas de design dos dados, design estratégico e design tático de forma que não será possível obter sucesso na implementação de técnicas de design tático sem aplicar primeiramente práticas do design estratégico.

A continuidade e aperfeiçoamento nas etapas do fluxo de modelagem do domínio ocorre conforme o avanço no processo de análise e definição dos objetos identificados no domínio de negócio.

As etapas de design, estratégica e tática, agrupam seus respectivos conjuntos de padrões e técnicas de acordo com a fase de maturidade da modelagem. Assim sendo, a etapa estratégica se aproxima da fase inicial e mais rudimentar da visão sobre os dados ao mesmo tempo em que a etapa de design tática começa a ser explorada quando já existe uma visão mais concreta do domínio e relações entre os componentes. Com isso, sendo vista como uma lapidação mais fina dos objetos pois iniciam uma transição em direção a transformarem-se em algo mais concreto e definitivo, refletindo com a maior precisão o domínio de negócio.

Assim, quando construímos nosso Domain Model utilizando a terminologia DDD (Domain-driven Design) visando orientar o fluxo do processo, transitamos entre a etapa de design estratégico, identificando Bounded Contexts (Contextos Delimitados) e Ubiquitous Language (Linguagem Ubíqua) passando para a etapa subsequente, a qual, evolutivamente, abrange características relacionadas ao design tático, identificando Entities (Entidades), Value Objects (Objetos de Valor) e Aggregates (Agregados) representando o último nível de abstração da modelagem dos dados.

Design estratégico

Design estratégico se refere a quebrar a complexidade do trabalho a ser realizado em grupos de maneira a avaliar as melhores formas de integrá-los. A etapa de design estratégico inicialmente oferece Design Patterns como Bounded Context (Contextos Delimitados) e Ubiquitous Language (Linguagem Universal). DDD (Domain-driven Design), em sua forma mais pura, trata-se de modelar uma Ubiquitous Language em um Bounded Context explicitamente definido. Contém componentes internos representados e referenciados dentro do contexto por meio dos termos existentes na linguagem universal definida (Ubiquitous Language).

Assim, necessitamos aprender a separar nossos modelos utilizando-os exclusivamente dentro de contextos delimitados (Bounded Contexts). A semântica presente nos Bounded Contexts expressam os significados dos termos utilizados, resolvendo problemas de ambiguidade, atuando como um delimitador entre os contextos.

Significa que dentro de cada contexto, para cada componente de software, existe um significado específico, que é referenciado por realizar determinadas tarefas ao relacionar-se com os demais componentes dentro da fronteira transacional a qual pertence.

Bounded Context

Quando estamos iniciando o processo de modelagem, o Bounded Context, é algo conceitual, residindo no Problem Space (Espaço do Problema). Contudo, na medida em que nosso modelo avança, adquirindo maior profundidade em suas definições e se tornando mais claro, seu Bounded Context visivelmente inicia sua transição para o Solution Space (Espaço da Solução). E passa a refletir, como etapa final do processo, toda a modelagem em formato de Source Code (Código Fonte).

Ao aprofundar-nos nas práticas de DDD (Domain-driven Design) explorando os limites do Problem Space (Espaço do Problema) realizamos análise estratégica em um nível mais abstrato, discutimos o design no sentido de objetivos e riscos identificados.

De maneira que, o Solution Space (Espaço da Solução), cujo o contexto abriga a solução final, sendo composta por seus respectivos Bounded Contexts (Contextos Delimitados) e descritos através Ubiquitous Language (Linguagem Universal), reflete apenas o resultado do esforço de análise estratégica e tática aplicados entorno do Problem Space (Core Domain), assim, acabamos com uma representação mais precisa dos objetos transitados em direção aos Aggregates (Agregados) visando refletir nosso modelo de dados permanentemente.

O modelo de domínio é implementado dentro dos Bounded Contexts por meio de seus artefatos de software separados para cada contexto. O modelo definido dentro dos limites de cada contexto reflete a linguagem que é desenvolvida pelo time para trabalhar e se comunicar dentro de cada Bounded Context no sentido de implementação de funcionalidades.

Ubiquitous

A linguagem é chamada Ubiquitous Language (Linguagem Universal) pois ao mesmo tempo que é utilizada entre todos os membros do time (incluindo os Domain Experts), também é implementada como Source Code (Código Fonte). É essencial que a Ubiquitous Language seja rigorosa e precisa em manter os significados dos componentes em seus respectivos Bounded Contexts e seus limites transacionais.

Limites transacionais se referem a menor unidade atômica necessária a respeito das Business Invariants (Invariantes de Negócio), ou seja, os limites transacionais dos componentes devem ser tão pequenos quanto possível, idealmente uma única transação para cada componente uma vez que não podemos considerar transações atômicas que transcendam seus limites contextuais de cada Bounded Context precisamos identificar Transaction Boundaries (Limites Transacionais), definindo as fronteiras transacionais relacionadas a cada contexto.

Ao praticar técnicas utilizando DDD (Domain-driven Design), aplicamos Design Patterns como Aggregates (Agregados) com o objetivo de expressar, em última instância, tais limites mencionados. Desta forma, Aggregates (Agregados) são responsáveis por encapsular Entitities e Value Objects além de manter as Invariants e Constraints (Regras de Negócio) consistentes e íntegras.

O padrão Aggregates desempenha um papel fundamental ao relacionar terminologias utilizadas no Source Code (Código Fonte) da aplicação aos conceitos e termos presentes na Ubiquitous Language (Linguagem Universal), onde podem existir diferentes significados entre contextos (Bounded Contexts) além de estarem fortemente acoplados às definições de limites de transação dos dados pertencentes a cada operação, podendo, os Bounded Contexts conter múltiplos Aggregates em sua composição.

Uma das razões para utilizarmos Microsserviços é permitir o baixo acoplamento entre os times com o mínimo de impacto e com velocidades diferentes entre as equipes, pois times autônomos, prontos para reagir a mudanças tão rapido quanto o surgimento das alterações em nosso domínio agregam maior valor ao negócio, além de reduzir custos operacionais relacionados à comunicação entre as equipes promovendo maior agilidade.

Agilidade

Ao adquirirmos determinada agilidade dentro dos times, começaremos a nos aproximar de algo cujo formato se parece com Microsserviços pois passamos a possuir maior autonomia na medida em que eliminamos dependências, ao passo de que, para conquistar tal nível de maturidade será necessário dedicação e esforço incomuns.

De maneira geral, implementar DDD (Domain-driven Design) na prática trata-se de um processo lento, complexo e cheio de armadilhas demandando esforço dos times envolvidos e responsáveis por transformar um conjunto de dados inicial, sem relações ou definições, contendo pouca representatividade, em um modelo negócio conciso e concreto capaz de refletir com a maior exatidão possível seu domínio de negócio.

Inevitavelmente, precisamos de algo como Domain-driven Design (DDD) para entender os modelos que usaremos para implementar nosso sistema e desenhar os limites transacionais entre os componentes. Assim sendo, ao assumir a responsabilidade de analisar o domínio de negócio utilizando DDD (Domain-driven Design) ajudará a implementar uma arquitetura de Microsserviços de forma mais assertiva em direção ao sucesso da aplicação.

Os princípios conceituais presentes no DDD (Domain-driven Design) se referem, de forma básica, a construção de uma linguagem compartilhada entre os envolvidos no processo (Ubiquitous Language entre Software e Business) identificando-se contextos e dentro de cada contexto definido (Bounded Context) adotar um significado para os termos utilizados, mantendo o foco em atacar a complexidade do domínio de negócio diretamente em seu núcleo.

Definir os Bounded Contexts são a primeira coisa a ser feita por meio de discussões entre o time e o pessoal de negócio, após, é necessário dar início a construção da Ubiquitous Language (Linguagem Universal), dando sentido aos componentes nos contextos apropriados.

Conceitualmente, deveremos ter um único time trabalhando por Bounded Contexts (Contextos Delimitados), com repositórios de Source Code (Código Fonte) separados por contexto, sendo ainda possível um time trabalhar em diversos Bounded Contexts porém múltiplos times não deveriam trabalhar em um único Bounded Context.

DDD

Cada time, responsável por determinados Bounded Contexts, define as interfaces de comunicação as quais serão utilizadas por quem tiver interesse em interagir com aquele contexto, sendo esta camada de isolamento um dos principais benefícios de se utilizar DDD (Domain-driven Design).

O grande motivo para utilizar-se Bounded Contexts e promover uma separação enxuta dos componentes identificados ao modelarmos o domínio, é que a maior parte dos times não sabem a hora de parar de jogar conceitos para dentro do modelo, e devido a esta falha acabam criando uma Big Ball of Mud (Grande bola de lama), obtendo-se um monólito em sua pior forma, a qual não existem motivos dos quais se orgulhar.

Ao aplicar DDD (Domain-driven Design) na prática sua organização explicitamente reflete as principais características de seu domínio de negócio. O design tático e suas técnicas são direcionadas a uma etapa de lapdação dos componentes de software de forma mais elaborada.

O padrão Aggregate é utilizado nesta camada pois apresenta maiores detalhes sobre como agregar entidades e objetos de valor dentro de clusters com tamanhos precisamente adequados da maneira mais explícita possível, de forma que o modelo começa pequeno e gerenciável, crescendo naturalmente em termos de complexidade, porém sem perder o controle sobre seus limites e relacionamentos entre os componentes.

Em um cenário cada vez mais competitivo, DDD (Domain-driven Design) auxilia você e seu time a entregar softwares que possuem design e implementação cada vez mais efetivos, e um design efetivo obriga a organização a pensar na construção de um modelo de domínio mais coerente.

Conforme identificamos o tamanho e complexidade de nosso domínio, ao modelarmos seus objetos, teremos diversos problemas de ambiguidade e para isso precisamos de limites. Para alcançar estes limites, delimitamos os contextos existentes dentro de nosso domínio, identificando e expressando seus artefatos utilizando Entities, Value Objects, e Aggregates gerando inicialmente um modelo contendo entidades que refletem o negócio.

Desta forma, em cima do modelo de domínio gerado, aplicamos os Bounded Contexts, limitando estas Entities e Aggregates por contexto explicitamente e juntamente com seus valores envolvidos. Estes limites (Boundaries), em alguns casos, acabam se tornando nossos Microsserviços naturalmente, em outros casos, componentes internos a estes Bounded Contexts refletem melhor cada Microsserviço sendo exposto, porém, em todos os casos, Microsserviços se referem aos limites que damos aos artefatos que identificamos dentro de nosso domínio bem como suas relações.

Ao modelar Microsserviços utilizando DDD (Domain-driven Design) devemos nos preocupar com a garantia de consistência dos componentes, do ponto de vista das regras de negócio, representadas dentro do contexto do DDD (Domain-driven Design) como Business Invariants (Invariantes de Negócio).

Queremos manter as Business Invariants intactas em nosso domínio aplicando DDD (Domain-driven Design), modelando estas Invariants dentro de nossos Aggregates (Agregado) em transações únicas, onde dentro dos mesmos ocorre todo o processo de validação da integridade dos dados relacionados a determinada instância em execução daquele Aggregate (Agregado).

Idealmente, devemos ter uma transação por Aggregate, podendo em alguns casos existir atualizações em múltiplos Aggregates dentro de uma mesma transação, situação a qual seria considerada uma exceção. Ainda, ao aplicarmos conceitos sobre DDD (Domain-driven Design) em nossa aplicação, outros padrões de design interessantes emergem como opções de implementação, como o conhecido Design Patterns CQRS (Command Query Responsability Segregation) possibilitando que possamos direcionar comandos aos Aggregates (Agregados), além de ser uma boa maneira de propagar as mudanças de dados (Data Changes) entre os Bounded Contexts.

Independentemente da maneira como o processo de modelagem utilizando DDD (Domain-driven Design) foi concebido em sua aplicação, o contexto é a chave que dará início ao processo de modelagem, possibilitando, a nós humanos, resolver qualquer problema de ambiguidade com ele em nossas mãos. Contextos são sobre a semântica dos dados, seus significados e representação dentro de cada universo, portando, em uma arquitetura de Microsserviços que promove o uso de DDD (Domain-driven Design), os dados são cidadãos de primeira classe.

A comunicação entre os Microsserviços, além dos limites de seus Bounded Contexts, em uma aplicação que promove a prática do DDD (Domain-driven Design), deveria ocorrer por meio de Domain Events (Eventos de Domínio), tanto do ponto de vista interno aos contextos de cada Microsserviço quanto ao externo, entre Bounded Contexts. Events também são utilizados para garantir a consistência eventual entre Transaction Boundaries (Limites Transacionais) e Bounded Contexts (Contextos Delimitados).

Desta forma, dentro do contexto de uso de DDD (Domain-driven Design), Events (eventos) tratam-se de estruturas imutáveis que capturam acontecimentos na linha do tempo podendo serem entregues aos componentes inscritos e interessados em reagir ao mesmo.

Prática

Praticar estratégias de DDD (Domain-driven Design) ajudam você e seu time construir software e obter benefícios sobre organização de domínio de modelos de dados coerentes, obtendo maior consistência de seus dados, além de facilitar decisões sobre design e integrações futuras, tornando seus componentes estrategicamente mais flexíveis e inteligentes quando aplicado o conjunto de técnicas e práticas proposto.

Uma vez que o processo fora executado com sucesso, basta dar uma olhada a fundo em seu domínio de negócio, refletido nos modelos identificados e seus dados o ajudarão a enxergar onde estão seus Microsserviços de forma intrínseca. Pois, o simples fato de utilizarmos frameworks como Spring Boot para desenvolver os componentes, não significa que estamos necessariamente implementando uma arquitetura de Microsserviços tampouco apresentando um design consistente sobre o modelo de dados.

Entender os princípios conceituais do DDD (Domain-driven Design) trata-se do ponto de partida em direção a compreensão do modelo proposto e para isso existem livros inteiros dedicados ao assunto com exclusividade e riqueza de detalhes, como Domain-Driven Design: Tackling Complexity in the Heart of Software escrito por Eric Evans, Implementing Domain-Driven Design e Domain-Driven Design Distilled escritos por Vaughn Vernon, Applying Domain Driven Design And Patterns escrito por Jimmy Nilsson, Patterns, Principles, and Practices of Domain-Driven Design escrito por Scott Millett e Nick Tune, entre outros.

Além destes autores renomados e com autoridade no assunto como Eric Evans, Vaughn Vernon, Christian Posta, Martin Fowler, Martin Kleppmann, Jimmy Nilsson, etc. é possível encontrar uma infinidade de artigos na internet, de outros autores, dedicados ao assunto DDD (Domain-driven Design).

Acompanhar a evolução de técnicas e práticas utilizadas na implementação do DDD (Domain-driven Design) em seus projetos exige grande curva de aprendizado pois para aprofundar-se nos conceitos e definições envolvendo desde o design estratégico até o design tático será necessário estudar a fundo o assunto, compreender os termos empregados ao mesmo tempo em que revisitamos definições presentes nos livros que servem de referência guiando suas iniciativas envolvendo DDD (Domain-driven Design) em direção ao sucesso.