APIs e Microsserviços

13 dez, 2018

Dicas para refatorar um monolito em microsserviços

Publicidade

O processo de transformar uma aplicação monolítica em microsserviços é uma forma de modernização de aplicação – algo que os desenvolvedores já fazem há décadas.

Como resultado, existem algumas ideias que você pode reutilizar quando estiver refatorando uma aplicação em microservices.

Uma estratégia é não fazer uma reescrita Big Bang. Ou seja, esqueça a ideia de reescrever toda aplicação do zero, direcionando todos os esforços do time para isso.

Embora muitas vezes isso soe interessante, é algo extremamente arriscado e geralmente resulta em problemas. Como Martin Fowler já disse algumas vezes, em uma tradução livre, a única garantia de um Big Bang é que algo vai explodir!

Ao invés de fazer uma reescrita Big Bang, você deve incrementalmente refatorar sua aplicação monolítica. Construa uma aplicação de microservices gradualmente, rodando cada novo microservice em conjunto do monolito original.

Com o passar do tempo, a quantidade de funcionalidades implementadas em microservices versus a quantidade de funcionalidades remanescentes no monolito vai evidenciar que o mesmo encolheu ao ponto de em algum momento sumir.

Martin Fowler refere-se à essa estratégia de modernização de Strangler Application (Aplicação Estranguladora). O nome vem da vinha estranguladora, uma espécie de cipó que cresce em árvores tropicais.

Esta vinha vai se fixando na árvore original, consumindo seus recursos enquanto ela própria cresce mais rápido que sua hospedeira, não raro causando a morte da árvore original e deixando no lugar um monte de vinhas no formato de uma árvore.

Estratégia 1 – Pare de cavar

A Lei dos Buracos (Law of Holes) diz que toda vez que você estiver em um buraco, você deve parar de cavar. Este é um ótimo conselho quando sua aplicação monolítica se tornar ingerenciável. Em outras palavras, você deve parar de tornar seu monolito maior.

Se tiver de implementar uma nova funcionalidade, você não deve adicionar esse código ao monolito. Ao invés disso, a grande ideia com esta estratégia é colocar o novo código em um microservice próprio pra isso. O diagrama a seguir, do site Nginx, mostra a arquitetura do sistema depois de aplicar essa abordagem.

Repare no uso de um request router à frente de ambas soluções (monolito e microservice) que pode facilmente ser um API Gateway. Essa camada de software recebe as requisições originais e verifica se elas devem ser direcionadas para o legado ou para o microservice.

O outro componente que surge com essa arquitetura híbrida foi chamado de Glue Code no diagrama, e nada mais é do que as dependências responsáveis por acesso a dados.

Geralmente o serviço usará libs e componentes de acesso a dados do monolito original. Com isso em mente, esse glue code pode usar uma de três estratégias:

  • Invocar uma API remota, fornecida pelo monolito
  • Acessar a base de dados do monolito diretamente
  • Manter sua própria cópia da parte da base de dados que lhe interessa, a qual deve estar sincronizada com a base do monolito

Este glue code muitas vezes é chamado de camada anti-corrupção. Isso porque este glue code evita que o micro serviço seja poluído por conceitos do modelo de domínio legado.

O termo camada de anti-corrupção foi introduzido pela primeira vez no livro Domain Driven Design, de Eric Evans, e depois foi refinando em um white paper.

Desenvolver uma camada anti-corrupção não é algo exatamente trivial, mas é essencial se quiser sair do inferno do monolito.

Implementar novas funcionalidades como um microsserviço tem uma série de benefícios. Essa estratégia impede que seu monolito se torne ainda mais ingerenciável, ao mesmo tempo que permite que o serviço possa ser desenvolvido, implantado e escalado de maneira independente do monolito.

A cada serviço novo que você cria, você experimenta um pouco mais dos benefícios desta arquitetura.

Entretanto, essa abordagem não endereça os problemas do monolito. Para consertar esses problemas, você precisa quebrar o monolito – falaremos sobre isso a seguir.

Estratégia 2 – Separe front-end e back-end

Uma estratégia que ajuda a encolher a aplicação monolítica é separar a sua camada de apresentação da sua lógica de negócio e do acesso a dados também. Uma típica aplicação corporativa consiste de ao menos três diferentes tipos de componentes:

  • Presentation layer: componentes que lidam com requisições HTTP e implementam APIs ou UIs. Em uma aplicação que tenha uma interface de usuário muito sofisticada, terá uma quantidade de código de front-end substancialmente grande
  • Business logic layer: componentes que são o core da aplicação e implementam as regras de negócio
  • Data‑access layer: componentes que acessam outros componentes de infraestrutura como bancos de dados e message brokers

Geralmente existe uma separação muito clara entre a lógica de apresentação em um lado e as regras de negócio e de dados em outro. Geralmente a sua camada de negócio terá uma API consistindo de uma ou mais fachadas/interface, que por sua vez encapsulam os componentes de lógica de negócio.

Esta API é o que permite fazer a separação do seu monolito em duas aplicações menores. Uma aplicação será o front-end, e a outra o back-end.

Uma vez separados, o front-end fará chamadas remotas ao back-end, sendo que o diagrama abaixo mostra como isso fica, antes e depois:

Separar um monolito dessa maneira tem dois benefícios principais. Ele permite que você desenvolva, implante e escale duas aplicações de maneira independente. Principalmente quando se fala de front-end e rápidas iterações, testes A/B, etc.

Outro benefício é que você combina com a estratégia #1 ao fornecer uma API do monolito para ser consumida pelos novos micro services que você desenvolver.

Esta estratégia, no entanto, é somente uma solução parcial. É bem comum que você troque um problemão gigante por dois problemas menores, mas que ainda são um problema a ser resolvido. Você terá de usar uma terceira estratégia para eliminar os monolitos restantes.

Estratégia 3 – Extrair serviços

A terceira técnica de refatoração é transformar os módulos existentes dentro de um monolito em micro services standalone. Cada vez que você extrai um módulo e o transforma em um serviço, o monolito encolhe.

Uma vez que você tenha convertido muitos módulos, o monolito deixará de ser um problema. Ou ele irá sumir ou vai virar apenas um serviço por si só.

Priorizando quais módulos vão virar serviços

Uma aplicação monolítica grande geralmente terá dezenas ou centenas de módulos – todos candidatos para extração. Saber quais módulos converter primeiro pode ser desafiador. Uma boa abordagem é começar com alguns módulos que são fáceis de extrair.

Isto lhe dará experiência com microservices em geral e com o processo de extração em particular. Depois que você extrair esses módulos mais fáceis, poderá partir para os mais importantes.

Converter um módulo em um microservice é algo que tipicamente requer tempo. Uma técnica de priorização é priorizar aqueles que mudam com frequência.

Uma vez que você tenha convertido um módulo desses para um serviço, você pode desenvolver e implantar ele de maneira independente do monolito, o que vai acelerar o seu desenvolvimento.

Outra abordagem interessante é extrair os módulos que possuem requisitos significantemente diferentes do resto do monolito. Por exemplo, é útil pegar aquele módulo que usa uma base de dados in-memory e transformá-lo em um microservice, o qual poderá ser implantado em um servidor com muita memória.

Da mesma forma, aquele módulo que consome muita CPU pode ser transformado em um microservice e feito deploy em um servidor com muito mais CPU do que RAM. Conforme você vai extraindo estes módulos específicos, você vai ver como se tornará mais fácil escalar os mesmos.

Como extrair um módulo

O primeiro passo ao extrair um módulo é definir a interface entre o módulo e o monolito. Geralmente será uma API bidirecional, pois é comum o monolito precisar de dados do microservice e vice-versa.

Se a sua lógica de negócio do monolito estiver com muitas associações entre suas classes , talvez seja difícil de expor apenas o que importa para o microservice utilizar e não é raro que uma refatoração interna no monolito seja necessária para continuar avançando com essa estratégia de extração.

O diagrama abaixo mostra o passo a passo da extração de um módulo de um monolito para um microservice:

Neste exemplo, o módulo Z é o candidato a ser extraído. Seus componentes são usados pelos módulos X e Y. O primeiro passo de refatoração é definir um par de APIs de alto nível. A primeira interface é de entrada e é usada pelo módulo X para invocar o Z. A segunda é uma interface de saída usada pelo módulo Z para invocar o Y.

O segundo passo da refatoração transforma o módulo em um serviço separado. Uma vez que você tenha extraído um módulo, é mais um serviço que você tem que lhe permite desenvolver, implantar e escalar mais facilmente do restante do monolito. Você poderá inclusive reescrever este serviço do zero mais tarde, uma vez que o módulo já foi isolado.

Cada vez que você extrair um novo serviço, estará um passo mais perto de ter uma aplicação 100% em microservices. Com o passar do tempo, seu monolito encolherá e a virada de chave entre as arquiteturas se tornará algo natural e inevitável.

Até a próxima!