Técnicas para migrar o seu sistema monolítico para microsserviços
Introdução
Esse artigo visa explorar algumas técnicas para migrar o seu sistema monolítico para microsserviços baseado, principalmente, na obra de Sam Newman, o livro: Migrando sistemas monolíticos para microsserviços. Apesar de tentar consolidar os principais aprendizados extraídos do livro através dessa resenha, indico que, ao final da leitura, busquem se aprofundar no assunto através da obra original.
Entendendo melhor o contexto
Primeiramente, precisamos entender os conceitos envolvidos, o que seria um sistema monolítico, suas vantagens e desvantagens, bem como o que é um sistema em microsserviços, vantagens e desvantagens.
Sistemas monolíticos
A palavra monólito pode ser conceituada como monumento ou obra constituído por um só bloco de pedra. Já um sistema monolítico, é aquele sistema único, este tem pelo menos três tipos:
- O de único processo (mais comum);
- O monolítico distribuído;
- E sistemas caixa-preta de terceiros.
Sam Newman se refere àquele sistema de uma unidade de implantação, quando todas as funcionalidades de um sistema tiverem de ser implantadas em conjunto.
Os sistemas monolíticos têm como vantagens:
- Subir rapidamente uma PoC ou MVP para validar um negócio ou produto;
- O ambiente de desenvolvimento fica mais simples quando a arquitetura é monolítica;
- Mais simples de testar, pois é possível testar a aplicação de ponta a ponta em um único lugar;
- Baixa comunicação entre redes;
- Topologia de implantação mais simples.
Esse modelo também tem as suas desvantagens, como:
- Dificuldade de escalar;
- Por ser construído em um único código, se algo quebra, todo sistema pode ficar indisponível;
- Manutenção difícil dado o crescimento da base de código;
- Baixa flexibilidade sobre as linguagens de programação do projeto.
Martin Fowler diz que:
Aplicativos monolíticos podem ser bem-sucedidos, porém frustrantes – especialmente quando mais aplicações forem implementadas na nuvem. Ciclos de desenvolvimento são amarrados – uma mudança feita em uma pequena parte do aplicativo requer que o monolito inteiro seja republicado. Ao/]. longo do tempo ficará cada vez mais difícil manter uma estrutura modular, o que torna mais difícil fazer com que mudanças afetem apenas um módulo. Escalar requer escalar o aplicativo inteiro, não apenas as partes que requerem mais recursos.
Microsserviços
Sam Newman conceitua que microsserviços “são serviços que podem ser implantados de forma independente e são modelados em torno de um domínio”. A forma de comunicação entre eles também é importante, sendo através de redes. Assim, essa arquitetura de microsserviços tem como base vários microsserviços colaborando entre si.
Uma das vantagens desses, os microsserviços são independentes de tecnologia, pois expõem as funcionalidades através de endpoints a serem consumidos através da rede, veja como um sistema distribuído.
Outra vantagem e característica é eles terem implantações independentes. Dessa forma, você pode atualizar (alterar e implantar) um serviço sem ter que atualizar o restante do sistema, ou seja, esqueça a necessidade de orquestrar entregas múltiplas de múltiplos times para que seja tudo entregue em um pacote só. Lembrando que, para que isso seja verdade, devemos garantir um baixo acoplamento.
Ao contrário de sistemas monolíticos, os microsserviços podem ser escalados independentemente. Essa vantagem pode auxiliar na distribuição adequada de recursos, redução de custos e muitos mais.
Além disso tudo, como o domínio está encapsulado e externaliza suas funcionalidades por endpoints, eles podem ser reutilizados em várias aplicações.
Citei algumas vantagens, mas não se limita a isso. Podemos trabalhar em paralelo nos serviços, incluir mais desenvolvedores para resolverem um problema sem que atrapalhem uns aos outros, reduzimos a complexidade cognitiva sobre o conhecimento de negócio e muito mais.
É claro que vendi um sonho com várias vantagens, mas nem tudo são flores. O modelo tem seus desafios. Entre esses desafios, podemos citar a comunicação entre eles, ou seja, redes – temos que nos preocupar por exemplo com latências, falhas na entrega de pacotes. Outro desafio é a consolidação e leitura de logs para uma potencial depuração. Também tem o desafio de lidar com stacks variadas em diferentes serviços.
Uma das discussões que sempre vem à tona é sobre o tamanho dos microsserviços. Este é um dos aprendizados do livro de Sam Newman – não se preocupe com o tamanho, ou número de linhas de código, mas sim com o negócio. No livro, ele foca em entender bem o domínio de negócio e por isso ele sugere a utilização de DDD (Domain-Driven Design). Não vou entrar no mérito de DDD, pois não é o foco desse artigo, mas com certeza vale muito aprofundar os estudos nesse tema.
Técnicas para migração
Antes de qualquer migração, é importante saber o porquê dela. O que você deseja alcançar com essa mudança, ter uma visão clara, pois essa mudança pode exigir um investimento significativo. Além disso, é normal demorar uma transição dessas, não é apenas questão da falácia do custo irrecuperável, mas as pessoas realmente esquecem o porquê de estarem fazendo o trabalho.
Não menos importante, muitos dos benefícios alcançados com uma arquitetura de microsserviços podem ser alcançadas de outras formas, darei apenas um exemplo. O desejo de aumentar a autonomia das equipes é frequente, aumenta a confiança, motivação, liberdade e muitos outros benefícios. Como fazer isso? Um possível caminho é fazer com que diferentes equipes sejam responsáveis por diferentes partes do sistema. Nesse caso, um monolítico modular ajuda muito. Outra forma é identificar pessoas com maior conhecimento sobre determinadas partes do sistema e empoderá-las com a tomada de decisão sobre aquela parte. Mais uma forma de aumentar a autonomia pode ser adotando abordagens self-service para provisionamento de máquinas, ambientes, acesso a logs.
Por fim, é importante saber quando não adotar microsserviços:
- Em domínios nebulosos;
- Startups;
- Software instalado e administrado pelos clientes;
- E quando não se tem um bom motivo!
Mas digamos que você está convencido que o melhor caminho a ser tomado para você é realmente migrar o seu sistema monolítico para microsserviços. Vamos falar, então, de algumas técnicas.
Aplicação strangler fig
Essa é uma técnica muito vista na reescrita de sistemas. O padrão, inicialmente identificado por Martin Fowler, é inspirado em um tipo de figueira que germina nos galhos superiores das árvores. A árvore existente serve como um apoio para a nova figueira, que, se chegar nos estágios finais, será possível ver a árvore original morrer e apodrecer, restando somente a nova figueira.
Olhando para software, a ideia é que o novo sistema tenha o suporte do sistema existente, permitindo coexistência, e que, ao momento que o novo sistema esteja pronto, possamos substituir o velho.
Esse padrão é baseado em três passos:
- Identifique as partes do sistema atual que você deseja migrar – uma das formas de fazer isso é com uma análise de custo-benefício;
- Implemente a funcionalidade em seu novo microsserviço;
- Desvie as chamadas do sistema monolítico para o novo microsserviço.
Chamo atenção para que, até a chamada para a nova funcionalidade seja feita, ela existirá em ambiente de produção, mas não estará tecnicamente ativa. Dessa forma, você tem tempo para desenvolver a nova funcionalidade até que esteja satisfeito com a nova implementação e gerenciamento do serviço.
Uma das ideias aqui é separar o conceito de implantação de lançamento. Não quer dizer que a funcionalidade, por estar no ambiente de produção, ela realmente será usada pelos clientes. Isso te dá a liberdade de testar a nova funcionalidade em produção antes que ela seja utilizada. Outro ponto importante é que essa abordagem de migração gradual nos permite um rollback com muito mais facilidade.
Composição de UI
A técnica anterior estava focada numa migração no lado servidor, mas a interface de usuário também proporciona ótimas oportunidades. Imagine uma funcionalidade parte servida pelo monolito e parte servida por uma nova arquitetura de microsserviços.
Uma das estratégias muito utilizadas nas migrações é a composição por páginas. Em vez de migrar tudo de uma vez, vão sendo atualizadas páginas, o que num momento de transição irá apresentar experiências distintas para os usuários quando utilizassem as partes novas do sistema ou site.
Outra estratégia é fazer isso a partir da composição de widgets. No livro de Sam Newman, em um dos seus cases, a ideia foi pegar uma parte de um site, com desafios interessantes, mas que também não fosse a parte mais em evidência do sistema. A ideia era colocar algo no ar, aprender com a experiência e garantir que, se houvesse algum erro, não afetasse o core do sistema. O exemplo apresentado é de um sistema de viagens, o qual decidiram apenas pela implantação de um único widget que exibia os dez principais destinos de viagens definidos pelo novo sistema. Aqui, foi utilizada a técnica Edge-Side Includes, usando o Apache, onde um template foi definido em sua página web e um servidor web fornece o conteúdo.
A composição de UI permite separar módulos distintos de UI que poderiam representar um formulário de pesquisa, um mapa, etc. A figura abaixo ilustra a ideia.
Outro caminho, ao invés da composição de múltiplas páginas, foi o de aplicações single-page, tendo uma interface mais eficaz e que executa tudo num só painel. Aqui, a ideia é de uma composição de widgets. Houve tentativas de formatos comuns, uma dessas tentativas foi através da especificação de Web Componentes, mas a demora para que esse padrão ganhasse força fez com que as pessoas buscassem alternativas. Uma alternativa que tem ganho bastante força no mundo de frameworks Javascript como Vue, Angular e React é a de Micro Frontends. O termo Micro Frontend ganhou força em 2016 propondo a mesma ideia de microsservices que eram utilizados em backend, só que, dessa vez, para frontend. Os princípios que sustentam o microfrontend são:
- Ser agnóstico em relação a tecnologia;
- Cada serviço ser isolado e autocontido;
- Convencionar nomenclaturas para local storage, cookies e outros para evitar conflitos;
- Priorizar recursos nativos do browser ao invés de APIs customizadas;
- Construir um site resiliente e utilizável mesmo quando tiver problemas para carregar o código Javascript.
Branch por abstração
Anteriormente, falamos do padrão strangler fig, no qual intercepta chamadas no perímetro do sistema monolítico. Mas e se a funcionalidade que desejamos extrair estiver muito enraizada? Desejamos fazer mudanças sem causar problemas para o sistema e tão menos para os desenvolvedores que trabalham naquela base de código. Isso nos leva a querer fazer alterações rápidas com o objetivo de evitar perturbações.
Normalmente, desenvolvemos código em branchs e, quando estas alterações estiverem prontas, fazemos o merge para a master. Quanto mais tempo essa branch existir, maiores serão os desafios para se fazer um merge. Então, a ideia aqui é conseguirmos desenvolver código de forma incremental, sem grandes perturbações e sem utilizar de uma branch de código de longa duração. A branch por abstração permite fazer alterações no código existente a fim de permitir que as implementações coexistam com segurança.
O padrão tem 5 passos:
- Criar uma abstração para a funcionalidade desejada;
- Mudar as chamadas para que usem a nova abstração;
- Fazer a implementação da abstração com a funcionalidade retrabalhada, a qual chamará o novo microsserviço;
- Fazer a troca da abstração para que a nova implementação seja usada e,
- Fazer uma limpeza da abstração e remover a antiga implementação.
Abaixo, tento ilustrar como seria cada uma dessas etapas. Imagine que temos um sistema fiscal que trata da NF-e (nota fiscal eletrônica), os títulos gerados, escriturações, ajustes, transferências, devoluções e por fim gerando uma apuração.
O primeiro passo seria selecionar a funcionalidade a ser substituída e criar uma abstração. Um dos aprendizados de Sam Newman é tentar iniciar por funcionalidades com um baixo número de inputs e outputs. Logo, a NF-e e a Apuração seriam os últimos a serem escolhidos. Nesse exemplo, irei escolher a escrituração para iniciar a decomposição do nosso monolito em microsserviços.
Conclusão
Nesse artigo, falei de algumas técnicas para decompor o seu sistema monolítico, mas existem dezenas de outras formas. No próprio livro de Sam Newman ele mostra outras técnicas, como:
- Execução em paralelo;
- Colaborador decorador;
- Captura de dados modificados;
- Sem falar nas decomposições relacionadas a banco de dados, que nem foram citadas nesse artigo.
Importante saber que, num projeto real, normalmente será necessário um mix de técnicas para ser bem-sucedido na decomposição de um sistema monolítico para microsserviços. Outro grande aprendizado foi de que não é preciso fazer uma migração dessas para se ter muitos dos benefícios de uma arquitetura de microsserviços, logo, tenha bem claro o motivo para fazer um trabalho desses.
Lembre-se de que só é gerenciado aquilo que se mede. Então, esteja bem munido de indicadores para acompanhar o andamento do projeto e o sucesso dado os objetivos definidos.
Por fim, entenda que é necessário envolver as pessoas num processo de migração desses, elas são fundamentais para o sucesso do projeto. Alinhe os objetivos, expectativas, apresente os indicadores e relembre estes de tempos em tempos.