Microsserviços: quando e por onde começar

Visão geral

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 microsserviços e como tem se dado o roadmap para planejar, desenhar e implementar microsserviços.

Motivação: em que momento os microsserviç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 microsserviç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 microsserviços?

De monólitos a microsserviços

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


Figura - Camadas de uma aplicação

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.

Figura - Design Pattern MVC


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.


Figura - Camadas de uma aplicação

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 microsserviç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 microsserviços

Um microsserviç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 microsserviç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 (microsserviç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 microsserviç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. Uma arquitetura de microsserviços se destaca por organizar uma aplicação como um conjunto de pequenos serviços independentes, onde cada serviço é projetado para atender a uma funcionalidade específica do sistema. Esses serviços comunicam-se entre si por meio de APIs leves, como REST ou mensagens de eventos, promovendo a separação de responsabilidades e o isolamento de funções. Essa abordagem possibilita uma arquitetura mais flexível, onde cada microsserviço pode ser desenvolvido, testado, implementado e escalado de maneira independente.

Essa independência é uma das grandes vantagens dos microsserviços. Quando comparada à abordagem monolítica, onde toda a aplicação é um bloco único, a arquitetura de microsserviços permite que as equipes de desenvolvimento trabalhem simultaneamente em diferentes partes do sistema sem que isso impacte a aplicação como um todo. Isso resulta em uma maior velocidade de entrega de novas funcionalidades, correções de bugs e melhorias de performance.

Os microsserviços trazem consigo alguns benefícios que são essenciais no contexto da computação em nuvem, como elasticidade e escalabilidade. Por exemplo, se uma determinada funcionalidade da aplicação é mais requisitada do que outras, o microsserviço que a suporta pode ser escalado horizontalmente (ou seja, replicado) para suportar essa demanda específica sem a necessidade de escalar toda a aplicação.

Enquanto a arquitetura de microsserviços traz inúmeras vantagens em termos de modularidade, ela também introduz desafios novos, principalmente em relação à comunicação entre serviços. Como cada microsserviço é um componente independente, a comunicação entre eles precisa ser bem planejada para manter a coesão e a consistência do sistema como um todo.

Um ponto importante é o protocolo de comunicação. Normalmente, padrões como REST ou gRPC são utilizados, mas a decisão de qual tecnologia adotar depende da natureza do projeto, dos requisitos de performance e da interoperabilidade necessária. É importante considerar que a comunicação assíncrona baseada em eventos (event-driven architecture) pode ser uma abordagem eficiente para a integração entre microsserviços, principalmente para cenários onde a baixa latência e a resiliência são essenciais.

Existe também a questão da transacionalidade. Em um sistema monolítico, transações complexas podem ser facilmente gerenciadas com o uso de frameworks de persistência e bancos de dados relacionais. Já em uma arquitetura de microsserviços, como cada serviço gerencia seu próprio banco de dados e lógica de negócio, as transações distribuídas se tornam um desafio. Abordagens como a aplicação do padrão “Sagas” ou o uso de técnicas de eventual consistency são necessárias para garantir a integridade dos dados sem comprometer a autonomia de cada microsserviço.


E quando devo considerar a migração de monólito para microsserviços?

Uma dúvida comum para equipes de desenvolvimento e líderes de tecnologia é entender quando é o momento certo para migrar de uma arquitetura monolítica para microsserviços. Essa transição, apesar de altamente benéfica em muitos cenários, também possui seus custos e riscos. Primeiramente, uma análise criteriosa da aplicação monolítica existente é essencial. Perguntas como “O quão difícil é lançar novas funcionalidades?”, “Quais são as barreiras para escalar partes específicas do sistema?” e “Existe uma alta interdependência entre diferentes partes da aplicação que dificulta a manutenção?” são indicativos da necessidade de uma migração.

A abordagem incremental geralmente é a melhor escolha, começando pelos módulos ou funcionalidades que causam mais problemas ou são mais críticos para o negócio. Uma técnica comum é identificar um “bounded context” bem definido dentro do monólito que possa ser extraído como o primeiro microsserviço. Essa migração gradual ajuda a diminuir os riscos e a garantir que os benefícios dos microsserviços sejam alcançados sem grandes interrupções na operação do sistema.

No caso de um projeto de greenfield, ou seja, quando um novo sistema está sendo construído do zero, o uso de microsserviços pode ser considerado desde o início, mas com cautela. Nem toda aplicação necessita da complexidade de microsserviços; para sistemas menores, um monólito modular pode ser uma abordagem mais simples e eficaz. Contudo, se a expectativa de crescimento é grande, a elasticidade, independência de desenvolvimento e potencial para escalabilidade tornam a arquitetura de microsserviços uma escolha estratégica.

Desafios e boas práticas na implementação de microsserviços

Apesar das vantagens claras, implementar uma arquitetura baseada em microsserviços traz consigo desafios significativos que precisam ser tratados com boas práticas de engenharia:

Observabilidade e monitoramento: Como os microsserviços são independentes e distribuídos, é crucial ter uma visão clara sobre o estado de saúde de cada serviço. Ferramentas de observabilidade que oferecem logs centralizados, métricas e rastreamento de transações (tracing) são essenciais para manter a operação confiável.

Gerenciamento de configurações e descoberta de serviços: Microsserviços precisam ser capazes de localizar uns aos outros em tempo de execução, o que pode ser complexo em um ambiente dinâmico como a nuvem. Ferramentas de service discovery, como o Consul ou o Kubernetes DNS, facilitam essa comunicação. O gerenciamento centralizado de configurações também é vital para manter a consistência e reduzir o risco de falhas.

Controle de versões e compatibilidade: Com a evolução contínua de cada microsserviço, é importante ter uma estratégia clara para versionamento de APIs e contratos de comunicação. Isso evita que mudanças em um microsserviço causem quebras inesperadas em outros serviços que dependem dele.

Segurança e autorização: Uma arquitetura baseada em microsserviços requer uma abordagem diferente para segurança. Estratégias como autenticação centralizada via OAuth2 ou OpenID Connect e a aplicação de políticas de autorização em nível de serviço são práticas recomendadas para proteger a comunicação entre serviços e dados sensíveis.

Automação de deploys e infraestrutura como código: Em um ambiente de microsserviços, a automação é essencial para garantir que cada serviço possa ser construído, testado e implantado de forma rápida e confiável. Ferramentas de CI/CD (como Jenkins, GitLab CI/CD, ou AWS CodePipeline) e soluções de infraestrutura como código (como Terraform ou CloudFormation) são indispensáveis para orquestrar o ciclo de vida dos serviços.

A arquitetura de microsserviços se apresenta como uma alternativa flexível e escalável para o desenvolvimento de software moderno, especialmente no contexto de aplicações cloud-native. No entanto, a transição de uma arquitetura monolítica para microsserviços requer planejamento cuidadoso e uma análise aprofundada das necessidades do negócio e da aplicação.

A adoção de boas práticas e ferramentas adequadas são fatores críticos para o sucesso na implementação e operação de uma arquitetura de microsserviços, garantindo que ela traga todos os benefícios esperados: modularidade, escalabilidade, resiliência e velocidade de desenvolvimento.

Referências:

[1] Fowler, Martin. Microsserviços prontos para produção. Novatec, 2017.

[2] Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.

[3] Newman, Sam. Building Microservices: Designing Fine-Grained Systems. O'Reilly Media, 2015.

[4] Richardson, Chris. Microservices Patterns: With Examples in Java. Manning Publications, 2018.

[5] Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. University of California, Irvine, 2000.

[6] Adzic, Gojko. Specification by Example: How Successful Teams Deliver the Right Software. Manning Publications, 2011.

[7] Cloud Native Computing Foundation. Cloud Native Landscape. Disponível em: https://landscape.cncf.io.

[8] ThoughtWorks. Evolutionary Architecture: Embracing Change. ThoughtWorks Inc., 2017.

[9] Bass, Len; Clements, Paul; Kazman, Rick. Software Architecture in Practice. Addison-Wesley, 2012.

[10] Kubernetes Documentation. "Service Discovery and Load Balancing". Disponível em: https://kubernetes.io/docs/concepts/services-networking/service/.

[11] AWS Documentation. "Designing Microservices on AWS". Disponível em: https://aws.amazon.com/architecture/microservices/.


Comentários

ARTIGOS POPULARES

A Sinfonia da Identidade: Como a música da adolescência molda o caráter dos nossos filhos

Reavaliando a empregabilidade em Ciência da Computação: Uma análise dos desafios de formação e mercado

As Três Leis Fundamentais da Ciência da Computação por Carlos Diego