Microserviços: quando e por onde começar [parte 1/2]

Com o advento da computação em nuvem, conceitos e abordagens arquiteturais de software até então consideradas “estado da arte”, passaram a se tornar padrões fundamentais em projetos de software. Isso porque mais do que simplesmente entregar aplicações como serviço, o que até então tínhamos como requisitos não funcionais tornaram-se premissas básicas na arquitetura de software. Características como elasticidade, adaptabilidade e portabilidade fazem parte deste contexto.

Arquiteturas cliente/servidor foram por muito tempo mais do que um modelo arquitetural. Tratava-se do “padrão viável” ante as possibilidades de infraestrutura.

O propósito deste artigo (em duas partes) é analisar a arquitetura de software baseada em microserviços e como tem se dado o roadmap para planejar, desenhar e implementar microserviços.

Motivação: em que momento os microserviços fazem sentido?

A primeira pergunta que fazem normalmente é: qual o momento em que você percebe que sua aplicação monolítica precisa ser redesenhada para uma arquitetura baseada em microserviços? Ou mais além: a considerar um cenário de greenfield, em que o software começará a ser desenvolvido do zero. Posso já começar pensando-o como microserviços?

De monólitos a microserviços

Arquitetura tradicional em três camadas: castelo não se constrói sobre estacas de palitos

Arquitetura em três camadas

Desde as bancas das universidades aos escritórios corporativos, o padrão de desenvolvimento em três camadas dominou — e ainda domina, diga-se de passagem — boa parte dos repositórios de software pelo mundo. Inclusive, quando falamos de padrões de projeto (design patterns), a pedra fundamental da engenharia de software é o MVC (Model, View, Controller).

Esse padrão é tão influente que praticamente todas as tecnologias de desenvolvimento possuem em seus principais frameworks a abstração para otimização da implementação deste modelo, leia-se: SpringMVC para Java, Django para Python, Rails para Ruby, entre tantos outros.

Naturalmente existem diversas formas de implementar esse padrão. Pode-se isolar o front-end (apresentação) com chamadas estáticas ao backend (aplicação e dados); pode-se combinar os três em um único bloco, caso não haja necessidade de um repositório externo; as três camadas isoladas, porém, compondo o mesmo pacote a ser executado por um servidor web, por exemplo. Analisando estritamente pela ótica do desenvolvimento, parece o modelo ideal. Principalmente considerando que nenhum projeto já nasce grande: geralmente é pouca gente, poucos requisitos e principalmente, poucos recursos para rodar essa aplicação. Tudo começa a mudar quando pensamos sob a perspectiva de “e se precisarmos crescer?”.

Motores da motivação: Tamanho, acoplamento e elasticidade

Aplicações começam pequenas

Nenhuma aplicação já nasce grande. Linhas de códigos não brotam do chão — ainda que os gerentes de projetos teimem em achar que sim — e leva tempo para que um mero CRUD se transforme em algo que se diga: sim, agora temos uma aplicação!

Partamos da premissa de que o crescimento de um software — em linhas de código — está diretamente associado a dois fatores: a) ao seu uso, ou seja, quanto mais usado, mais momentos “poxa, seria legal se ele também fizesse isso ou aquilo” acontecerão; b) aumento da carga de trabalho, onde aí o dimensionamento da infraestrutura da aplicação começa a ser posto a prova. Neste segundo caso o problema é ainda mais abragente, já que crescer tanto horizontalmente — mais requisições e acessos a sua aplicação fazem com que mais unidades computacionais sejam necessárias — como verticalmente — fazer com que cada unidade computacional dessa também cresça para suportar e processar aquela tarefa que ela adicionou a sua fila.

Quando um problema encontra o outro e produz um alien

Como comentei anteriormente, quando o ciclo de adição de novos recursos na aplicação x aumento da carga de trabalho começa a se tornar mais e mais intenso, os problemas também começam a ficar cada vez maiores.

Isso porque, novos recursos na aplicação significam mais linhas de código, mais demanda por testes, mais pontos de integração, mais dependência e assim sucessivamente. Somado a isso, está a operação da aplicação. Quanto mais acessos, quanto mais execução, maior a demanda de infraestrutura.

A estratégia natural para atender a este aumento de demanda é escalar horizontal e verticalmente a infraestrutura da aplicação. Ainda que essa abordagem aparentemente resolva o problema, pensemos em escala, ou seja, esse cenário multiplicado por 100, por 1.000, por 1.000.000… Colocar uma simples correção em produção se torna um acontecimento de escala global.

Adaptado de Production‑Ready Microservices: Building Standardized Systems (FOWLER, Susan. 2017)

Percebe-se que o problema de uma arquitetura monolítica está mais relacionado a sua capacidade de ser escalável de forma racional do que, propriamente dito, a sua natureza como padrão de projeto. Como Fowler (2017) salutarmente afirma “escalabilidade requer simultaneidade e segmentação: duas condições que são difíceis de serem obtidas em um monólito”.

Ainda sobre os desafios de um monólito em nível de infraestrutura, está o fato de que escalabilidade está relacionada a capacidade de uma infraestrutura crescer conforme a demanda. No entanto, quando falamos de aplicações born in the cloud, esta característica vai além, no que chamamos de elasticidade.

Enquanto a escalabidade possibilita a adição de capacidade conforme o aumento da demanda, a elasticidade viabiliza tanto a adição como a redução de capacidade conforme a demanda.

Do projeto a construção: monólitos x microserviços

Arquitetura de aplicação monolítica

Uma aplicação monolítica geralmente é descrita como software em que tanto o front-end como o back-end estão juntos em um único pacote de software. Ou seja, ainda que seu código fonte esteja em camadas (como no MVC), o build final é implementado de forma única.

Uma das principais características de aplicações monolíticas refere-se a modularidade. No geral, trata-se de uma característica importante quando analisada a perspectiva de reuso de software, manutenabilidade de partes ou estruturas específicas, alocação de infraestrutura para execuções de rotinas individuais e não para todo o pacote de software, entre outras características.

Em um monólito, todas as funções estão dentro de uma única aplicação. Naturalmente, cada função de um sistema possui diferentes escopos, demanda por recursos e etc. Em um monólito, se for necessário implementar alguma atualização, ou mesmo provisionar mais recursos para que ele processe mais rapidamente uma determinada função, será necessário tanto reimplantar toda a aplicação — ainda que para atualizar uma pequena parte dela — e reprovisionar a infraestrutura com base nesse novo cenário.

Arquitetura de aplicação baseada em microserviços

Um microserviço nada mais é do que uma aplicação, que geralmente executa apenas uma tarefa, no entanto, a faz de forma independente e mais eficiente — já que terá para si recursos dedicados. Esta “componetização” da aplicação viabiliza uma série de aprimoramentos no que diz respeito a manter, atualizar e escalar aplicações.

Primeiramente, quando há necessidade de alguma alteração de um microserviço, o processo é mais simples: a) por se tratar de uma aplicação específica, alterações se tornam menos complexas de serem realizadas; b) a manutenabilidade da aplicação (microserviço) torna-se mais simples; c) caso uma função da aplicação esteja requerendo mais infraestrutura, pode-se adicionar recursos especificamente para aquela função.

Em suma, a adoção de uma arquitetura voltada a microserviços permite que sejam tratados aspectos como redução na perda técnica, maior produtividade por parte dos desenvolvedores, curva de aprendizado menor, melhor eficiência nos testes e finalmente, elasticidade.

O que veremos na parte 2/2?

No próximo artigo, exploraremos: a arquitetura de um microserviço; e quando iniciar um projeto de migração de aplicações monolíticas para microserviços. ;-)