Granularidade em Microserviços: Granularity Integrators

Marcelo M. Gonçalves
Sicredi Tech
Published in
6 min readApr 1, 2024

Ao contrário dos desintegradores de granularidade, os quais propõem drivers para justificar a fragmentação dos serviços, a função dos integradores representa um trabalho no caminho inverso, sugerindo tanto o agrupamento de serviços existentes quanto a opção pela não fragmentação em estágios iniciais. Analisar cuidadosamente os trade-offs entre os drivers desintegradores/integradores permitirá encontrarmos bons níveis de granularidade entre os componentes (existentes e novos) pertencentes ao nosso projeto, presentes em todas as camadas de abstração, incluindo: negócio, aplicações, serviços, persistência, etc.

Os drivers desintegradores/integradores de granularidade estão descritos em detalhes no livro Software Architecture: The Hard Parts Neal Ford e Mark Richards.

Os architectural drivers mantêm laços diretos com os drivers de granularidade, sejam de fragmentação ou agrupamento, referindo-se exclusivamente aos requisitos não funcionais relacionados aos atributos de qualidade e principais restrições impostas pelos requisitos de negócio. Durante a fase de design estratégico, o domínio nos dirá explicitamente o que precisaremos agrupar e o que precisará ser separado como parte do processo de descoberta dos seus elementos/características.

Alguns atributos de qualidade explorando os requisitos não funcionais, podem ser citados: Performance, latência, escalabilidade, responsividade, taxas de transferência (throughput).

No contexto de sistemas distribuídos, os dados espalhados ao longo do cluster adicionam complexidade no gerenciamento da sua consistência e relacionamentos. Características como alta disponibilidade e performance com baixa latência e alto throughput impõem limitações ao processo de granularidade. Na prática, de forma a facilitarmos o controle transacional, os dados/modelos (dependência e relacionamentos) compõem o principal driver para direcionamentos de integradores de granularidade.

De forma, ao administrarmos (reduzirmos ou aumentarmos) o acoplamento dos dados, teremos os limites da transnacionalidade como força primária ao considerarmos os tipos de comunicação adotadas em sistemas distribuídos. As dimensões a serem consideradas são comunicação (sync/async), consistência (atomic/eventual) e coordenação (orchestrated/choreographed) devendo considerar as complexidades de gerenciamento dos dados de forma distribuída, seja através da consistência eventual ou atomicidade/strong.

Transações: Persistência e Base de Dados

A maior parte das aplicações monolíticas, incluindo alguns serviços coarse-grained, garantem a consistência dos seus dados confiando sua integridade a uma única unidade transacional atômica: ACID. As transações de base de dados impactam diretamente a granularidade dos serviços, tornando-se um importante norteador durante o trade-off de agrupamento de funcionalidades entre serviços.

A fragmentação dos serviços pode apresentar problemas (ex: delimitações pela segurança) onde a resolução será optar pelo reagrupamento. ao separarmos serviços relativamente coesos e consequentemente suas tabelas em base de dados, perderemos o suporte às (ACID) Transactions necessitando reverter manualmente diante da possibilidade de inconsistência nos dados.

“A tentativa de dividir um módulo coeso só resultaria em maior acoplamento além legibilidade reduzida.” — Larry Constantine

Desta forma, em casos de necessidade em manter unidades atomicamente consistentes, para cenários onde distribuí-las não seja considerado (BASE Transactions, 2PC — Two-phase Commit) o driver sugere a união dos serviços envolvidos na delimitação transacional, devolvendo o suporte a atomicidade/integridade através das (ACID) Transactions.

Workflow/Orquestração e Coreografia

A coordenação durante a comunicação interserviços ocorre de duas formas: orquestrada vs coreografada. Conforme os serviços avançam em direção a fragmentação (fine-grained) ou retrocedem ao agrupamento (coarse-grained), os impactos tornam-se evidentes no contexto de tolerância a falhas, incluindo elementos relacionados à disponibilidade/confiabilidade devido ao acoplamento entre múltiplas requisições (síncronas, assíncronas, eventuais, atômicas). Desta forma, independente do tipo de comunicação/coordenação/consistência, funcionalidades fortemente acopladas e dependentes entre si não devem ser fragmentadas.

Independente do estilo de comunicação (sync, async), facilidades em operações de commits/rollbacks (2PC) ou rollbacks complexos através de processos de compensação, precisamos criar consistência nos dados, tornando-os íntegros. Na prática, os Transactional Saga Patterns podem ser adotados de forma a trabalharmos adequadamente de forma a combinar dimensões como comunicação (sync vs async), coordenação (orquestrado vs coreografado) e consistência (atomic vs eventual), adaptando-as individualmente para cada cenário.

A tolerância a falhas pertence ao grupo de desintegradores de granularidade, adicionalmente referenciando seu papel nos workflows

A performance geral e responsiveness possuem funcionamentos distintos a depender do tipo de fluxo construído: orquestrado ou coreografado. Adicionalmente, a latência final agregada a cada salto nos serviços durante a comunicação precisa ser contabilizada. Na prática, ao mesmo tempo em que a queda de um serviço participando de uma orquestração pode tornar inoperável o fluxo de negócio, pagamos o preço pela maior latência ao resolvermos o problema através de coreografia.

Evidentemente, ao misturarmos os serviços envolvidos e uma business transaction, ganharemos latência aumentando a performance e responsividade ao preço do acoplamento. Em pipelines orquestradas, a criticidade de uma operação mal posicionada reflete os impactos transnacionais diretamente na integridade dos dados. Quanto mais operações precisam ser chamadas de forma a completarmos uma requisição de negócio, mais riscos são adicionados comprometendo a confiabilidade do fluxo. Precisamos considerar a probabilidade através do número de requisições a serem efetuadas levando em conta sua atomicidade. Considerado como direcionador para os drivers workflow vs coreografado.

Dados: Dependências e Relacionamentos

Essencialmente, os dados e suas dependências devem ser considerados parte fundamental ao nos referimos a arquiteturas de software distribuídas, onde os relacionamentos em camadas de persistência impactam diretamente a granularidade dos serviços. O relacionamento entre dados representa trade-offs importantes com relação aos rumos para os drivers desintegradores/integradores.

Arquiteturas distribuídas não resumem-se aos microservices, em todos os casos posicionando os dados ao centro das decisões referentes a granularidade (context/service boundaries), compondo a base para a transnacionalidade dos dados. A separação implícita dos schemas de base de dados em relação aos microservices apresenta a impossibilidade de utilização da função joins, obrigando a gestão do mapa das data-dependencies manualmente, operação essencialmente simples quando executada em contexto não distribuído (monolítico/macro-service). A busca pelos níveis de isolamento corretos implicará em round-trips adicionais e desnecessários.

A complexidade dos relacionamentos em base de dados relacional impedem/dificultam sua migração para um contexto distribuído. Desta forma, o resultado resume-se no compartilhamento dos dados delimitados por contexto de utilização, naturalmente incluindo um conjunto de tabelas no schema de cada bounded-context sob-demanda durante a construção da linguagem ubíqua. Deste ponto em diante, precisamos garantir a integridade das regras de negócio dispostas em base de dados distribuídos, tendo cada serviço responsabilizando-se pelos seus dados de forma a tornar obrigatória a solicitação por permissão de acesso aos dados pelos agentes externos ao seu contexto.

A consolidação dos dados em um modelo único, baseado na dependência/coesão, sugerem o agrupamento de serviços de forma a ganhar latência, melhorar o controle de tolerância a falhas incluindo melhorias de escalabilidade se considerarmos problemas relacionados a comunicação inter serviço. Quando comparadas a quantidade de trade-offs referentes aos integradores de granularidade, a dependência e relacionamento entre dados possuem a menor quantidade.

Considerações Finais

Arquiteturas de MSA (microservice architectures) definem serviços com propósitos quase únicos, distribuindo as camadas de infra+redes (service mesh), aplicação e arquitetura. A separação apresenta vantagens como autonomia/independência ao preço da imposição de complexidades diversas aos building-blocks decorrentes de uma fragmentação.

Afinal, o que significa ser micro e qual sua relação com o tamanho real que as coisas devem ter? Quais atributos precisamos considerar para chegarmos a uma resposta aceitável? A resposta correta simplesmente não existe pois cada domínio será exclusivo/particular. Para isso, a existência de técnicas/padrões, referidas como disintegrators/integrators drivers podem ajudar no direcionamento de forma a obtermos respostas realistas porém sempre acompanhadas de trade-offs.

Serviços excessivamente pulverizados podem apresentar alto grau de acoplamento em intercomunicação, consequentemente apresentando problemas de performance e acoplamento em camadas de aplicação.

Não existem balas de prata e desta forma, tanto os desintegradores quanto integradores de granularidade, no fim do dia devem cumprir princípios claros com propósitos específicos: melhorar a produtividade nos times, aumentar a velocidade das entregas, garantir que os requisitos não funcionais (performance, segurança, escalabilidade, etc) estejam alinhados às decisões sobre granularidade, adicionar simplicidade de arquitetura para facilitar manutenções a médio prazo, fornecer agilidade ao negócio durante as tomadas de decisão vinculadas ao planejamento estratégico da empresa.

Os impactos diretos da granularidade aos serviços/funcionalidades podem ser gigantes, inclusive modificar o curso dos negócios de uma empresa. Desta forma, as funcionalidades a serem mapeadas, tamanhos adequados, etc precisam ser apresentados em estágios iniciais de qualquer processo de design. Naturalmente, os elementos mencionados referem-se a uma jornada complexa com diversos trade-offs e decisões difíceis. Assim, a busca pela granularidade perfeita trata-se de um processo evolutivo e não um destino certo, onde nem sempre teremos todas as informações disponíveis para tomadas de decisão assertivas.

--

--