Arquitetura

Qual o tamanho adequado da granularidade de micro serviços?

 A arquitetura de micro serviços (MSA – microservice architecture) vem mudando o cenário de sistemas distribuídos há alguns anos. 

Desde o início das discussões de micro serviço, o assunto “tamanho” nunca foi um  tema que trouxe uma certeza de ideal para os desenvolvedores de software

A discussões de qual deveria ser o tamanho certo e a quantidade des serviços sempre são pautas entre os desenvolvedores de software. 

Nós trabalhamos desde 2018 com cerca de 80% da arquitetura baseada em micro serviços. 

Os benefícios dos micros serviços é uma questão indiscutível, já que favoreceu um crescimento de negócio, técnico, agilidade, escalabilidade, desacoplamento, entre outros. 

Nesse tempo passamos por diversas dessa discussões, e claro que nunca chegamos a uma fórmula mágica ou um caminho que consigamos dizer que é o ideal para todos os cenários. 

O tempo nos ensinou que alguns caminhos escolhidos na arquitetura de micro serviços não são tão bons assim, ao menos para a nossa realidade. 

No início optamos por levar a risca esse o conceito de micro serviços, onde cada serviços deveria ter uma única responsabilidade, um banco apenas, poucos endpoints.  

Com o passar do tempo vieram alguns problemas e alguns pontos que nos fizeram pensar e dar alguns passos atrás, são alguns:

Problema custo  

Nos deparamos com o primeiro problema, serviços com poucaresponsabilidade, porém tão pouca responsabilidade que não se justificaria sua existência solitária. E isso veio com um agravante “custo”, seja ele financeiro ou operacional. 

Se pararmos para pensar um serviço, por menor que seja, considerando um contêiner com um micro serviço, já inicia com um percentual de CPU sendo consumido e um percentual de memória – que no início são insignificantes, porém se colocarmos isso vezes 100 ou vezes 200 podemos imaginar o quanto isso fica grande.  

Se pensamos o quanto de máquinas teríamos para suportar 100 micro serviços com 256 mb de memória para um responsabilidade tão baixa, quantos bancos de dados deveríamos ter para suportar esses serviços com responsabilidade baixa?  Com a quantia de logs, monitoramento que é necessário essa quantia de serviços, questionamos se esse era o caminho correto.

Problema rastreabilidade 

Em seguida, nos deparamos com mais um problema que essa abordagem nos apresentou, a rastreabilidade. Imaginamos então um cenário onde o usuário fez login no seu aplicativo e essa única chamada, se desencadeou em 10 ou 20 apenas para uma operação. Quando tivermos um problema teremos 10, 20 possíveis lugares de pontos de falha.  

Se a rastreabilidade não estiver 100%, com ferramentas adequadas para esse troubleshooting, ela passa a ser um problema – que pode envolver várias pessoas, vários times em um algo que deveria ser muito simples de identificar. 

Problema escalabilidade 

A escalabilidade em um ambiente tão segmentado e granular pode virar um problema. No mesmo cenário de um usuário executando uma operação em seu aplicativo, sabendo que essa operação que desencadeia 10 ou 20 chamadas de micro serviços, provavelmente a maior parte deles deve subir horizontalmente para atender as requisições, dependendo de um certo volume, todos os bancos de dados também deve estar preparados para esse volume, que pode ser sazonal ou definitivo. O pool de conexões deve subir também para receber esses novos serviços que estão subindo. 

Mais uma vez o problema custo (financeiro ou operacional), rastreabilidade aparecem, a complexidade começa a evoluir, a manutenibilidade começa a ficar difícil. 

Problema de Manutenibilidade 

Alguns conjuntos de serviços passaram a ter um custo alto de manutenção. Quando um cenário de manutenção é necessário mexer em 5 ou mais serviços para 1 funcionalidade, isso parece estranho. E, pior: precisamos fazer deploy de todos no mesmo momento para a funcionalidade nova ou a que está sendo desenvolvida fazer sentido. 

Problema de Testes 

Ainda nesse cenário de altamente micro e altamente distribuído, o problema testes fica cada vez mais evidente. Se não houver uma maturidade alta para testes de todos os serviços que compõem uma feature, com certeza não estaremos levando até produção um código confiável. 

Nesse cenário podemos observar que os testes unitários não são o suficiente, já que a regra de negócio estava bem distribuída e só faria sentido com um teste mais abrangentes. 

Então se faz uma extrema maturidade e necessidade de alta cobertura de testes unitários, muitos testes de contrato, testes de integração, performance entre outros. 

Após algum tempo nesse cenário que passou a ser frequente, chegou a hora de parar e analisar o cenário atual. 

A empresa crescendo, novos clientes, ações de marketing frequentes… Seguir assim poderia ser cada vez mais problemático para um futuro sustentável. 

Paramos então para avaliar o que algumas empresas estão fazendo nesse sentido, o que a história do mudo de software tem para nos contar. 

Livros como DDD (driven domain design), SOA Principles of Service Design e alguns artigos, como MonolithFirst, nos contaram algumas histórias e nos refrescam a memória para dar o que consideramos, “um passo atrás” para chegar a uma futura solução. 

A figura abaixo representa bem a ideia de “passo atrás” que estamos falando aqui. Ou seja, nesse momento estamos falando de SOA, conceitos que não devem ser esquecidos.  

Daremos então esse “passo atrás”  e pensamos agora em domínio DDD. Palavra chave desse passo, pensando em domínio começamos a ver que antes de criar novos serviços, precisamos pensar, a qual domínio ele pertence? Esse domínio que ele pertence está bem conceituado? 

Definindo o domínio alguns problemas passaram a ser resolvidos. A quantidade de micro serviços passou a cair, a manutenção passou a fazer mais simples, a escalabilidade passou a ser mais objetiva, quando ocorria erro eram menos lugares para se verificar logs.    

Testes passaram agora a fazer mais sentido, um teste unitário de fato estava testando código de negócio. Testes de performance e integrações passaram a ser mais simples de analisar.  

Combinado esse tema como o MonolithFirst, artigo escrito por Martin Fowler em 2015, as coisas pareceram se encaixar.

Se considerarmos que um serviço deveria iniciar como um monolito, porém modularizado, com domínio bem definido – levando alguns conceitos de DDD como  Bounded Contexts e como Design by Contract – os micro serviços (ou se é que podemos chamar micro nesse momento) parecem estar mais adequados para uma futura segmentação.

Dependendo do caso, sim, ele deve ser bem segmentado. Em outros apenas deve ser observado – por exemplo, quando os serviços começam a ter algumas responsabilidades que ganham mais peso à individualidade, ou quando começam a ter Endpoints com mais requisições que os demais, onde manutenção e evolução é mais frequente, impactando features que não fazem sentido impactar. 

Podemos considerar que estamos falando de um cenário específico. Isso não quer dizer que isso é a realidade para todas as empresas e todos os cenários.

Como arquitetura de sistemas devemos lembrar que, ao longo do tempo, qualquer decisão causará impacto (seja ele positivo ou negativo) e eses impacto pode ser difícil de mudar, gerando um grande custo operacional, problemas para times etc.

Conclusão

A essa altura do campeonato já sabemos e podemos concluir duas coisas: serviços muito grandes são ruins por diversos motivos, pois são monólitos, e serviços muito pequenos também são um problema. Mas o que podemos considerar e que nos traz perto da idealidade é o DDD. Pensar no seu domínio, pois é ele que faz com que as coisas façam sentido e tragam o que realmente importa para a mesa. 

Teremos mais certeza de qual velocidade, manutenibilidade e testabilidade é o ideal quando soubemos mais do que se trata o business que estamos trabalhando. 

%d blogueiros gostam disto: