Microserviços reativos com Vert.x: introdução

Compartilhe

Compartilhar no facebook
Compartilhar no google
Compartilhar no twitter
Compartilhar no linkedin

A adoção de arquiteturas reativas vem ganhando espaço entre os desenvolvedores nos últimos anos. Isso pode ser justificado pelo fato de que aplicações reativas geralmente lidam com variações de demanda e carga de maneira mais eficiente.

Aplicações reativas geralmente também apresentam maior capacidade de recuperação em situações de falha (capacidade chamada de resiliência) e responsividade próxima ao tempo real de maneira simples e natural. 

Estes pontos são, sem sombra de dúvidas, interessantíssimos em um cenário cada vez mais comum no desenvolvimento de software: em arquiteturas baseadas em microserviços.

Uma breve introdução às arquiteturas reativas

Microserviços reativos com Vert.x

Será que você prepara um bolo de maneira reativa? (Image by Дарья Яковлева from Pixabay)

De maneira geral, arquiteturas reativas são arquiteturas que focam na reação à ocorrência de eventos e, por isso, são naturalmente assíncronas. Além disso, arquiteturas reativas tratam os dados trafegados como fluxos de informações assíncronos que podem ser propagados para diferentes pontos de maneira independente.

Imagine que você precise fazer um bolo com cobertura. A massa deste bolo precisará ficar 20 minutos em um forno para que fique saborosa… Você também quer assistir televisão em conjunto com essas atividades. Se fôssemos pensar de maneira mais tradicional (ou seja, uma maneira mais imperativa), poderíamos organizar estas ações da seguinte maneira

  1. Preparar a massa do bolo;
  2. Colocar a massa do bolo no forno;
  3. Aguardar 20 minutos sem fazer nada;
  4. Preparar a cobertura;
  5. Colocar a cobertura sob a massa do bolo;
  6. Assistir televisão.

Com uma abordagem mais “tradicional”, nós teríamos um “passo-a-passo” similar a este. Veja que o passo 3 não parece ser muito eficiente, tendo em vista que nós ficaríamos 20 minutos sem fazer absolutamente nada…

Além disso, se ocorresse um erro no passo 3, nós fatalmente não iríamos prosseguir para os passos seguintes, interrompendo abruptamente o nosso fluxo de processamento, por mais que os passos seguintes não tivessem ligação direta com a preparação da massa do bolo.

Se repensássemos neste fluxo de maneira mais reativa, poderíamos ter o seguinte “passo-a-passo”:

  1. Preparar a massa do bolo;
  2. Colocar a massa do bolo no forno;
  3. Colocar o celular para despertar depois de 20 minutos;
  4. Preparar a cobertura enquanto o bolo assa;
  5. Assistir televisão enquanto o bolo ainda está assando;
  6. Escutar o alarme do celular;
  7. Colocar a cobertura sob a massa do bolo;

Veja que este fluxo parece ser mais interessante que a primeira versão. Neste fluxo, nós temos algumas vantagens bem interessantes:

  1. Nós podemos desempenhar atividades independentes em paralelo (como preparar a cobertura e assistir televisão enquanto a massa do bolo assa) sem prejuízo para o produto final (o bolo em si é feito da mesma maneira);
  2. Caso um erro ocorra em um dos passos, os demais passos não são afetados diretamente. Se ocorrer uma falha no preparo da massa do bolo, nós teríamos ganhado tempo, pois já teríamos preparado a cobertura e assistido televisão. A preparação da massa do bolo não bloqueia a preparação da cobertura e não nos impossibilita de assistir televisão.

A segunda versão do nosso fluxo é uma versão que segue conceitos de programação e arquiteturas reativas.

A partir deste exemplo, podemos enumerar características próprias de arquiteturas reativas:

  1. Programação reativa envolve lidar com código assíncrono. Em arquiteturas reativas, é natural que múltiplos eventos ocorram ao mesmo tempo. Em nosso exemplo, nós poderíamos preparar a cobertura e assistir televisão enquanto a massa do bolo estava no forno, sem que isso prejudicasse o processo como um todo;
  2. Programação reativa envolve lidar com atividades não-bloqueantes. A execução de um fluxo específico em uma arquitetura reativa não pode bloquear a execução de outros fluxos que podem ser executados em paralelo. No exemplo do bolo, colocar a massa no forno, preparar a cobertura e assistir televisão são eventos não-bloqueantes, tendo em vista que eles podem ocorrer de maneira assíncrona e em paralelo sem prejuízo ao processo como um todo;
  3. Arquiteturas reativas são orientadas a eventos e mensagens. Em nosso exemplo, nós retiramos a massa do forno e terminamos de preparar o bolo quando o despertador do celular toca. Nós temos uma mensagem – o toque do despertador do celular – que dispara um evento – o término da preparação do bolo;
  4. Arquiteturas reativas tratam os dados como fluxos de informações que podem ser repassados entre vários processos. Acima, temos o celular, que poderá despertar dentro de 20 minutos. O celular é uma informação que trafega entre diferentes processos como se estivesse em um fluxo de dados. Ainda no exemplo acima, o celular trafega entre dois eventos: o preparo da cobertura e o momento onde assistimos televisão. Se necessário, esse tráfego ainda poderia ser dividido em dois processos concorrentes: seria o caso se uma pessoa preparasse a cobertura e outra pessoa assistisse televisão enquanto o celular não desperta para avisar ambas que a massa do bolo está pronta.

Assincronia, eventos não-bloqueantes e orientação a mensagens e eventos são características básicas da programação reativa e de arquiteturas reativas.

Vert.x: trazendo o mundo reativo para a JVM

vertex
Quando falamos sobre a JVM, existem várias opções de frameworks e bibliotecas para a criação de aplicações reativas. Alguns exemplos são o Akka e o Spring Reactor. Aqui na MovilePay, estamos utilizando uma outra opção bem interessante: o Vert.x, da Eclipse Foundation.

O Vert.x não é um framework, nem uma biblioteca: trata-se de um toolkit. Isso quer dizer que o Vert.x não oferece um modelo de desenvolvimento como o Spring (um framework) ou regras de utilização como o SL4J (uma biblioteca): trata-se de um conjunto de ferramentas open source extremamente rápido e eficiente para criação de aplicações reativas.

Isso faz com que o Vert.x seja uma ferramenta não-opinativa: você pode utilizar o Vert.x da maneira que for mais adequada para cada situação. Você pode até mesmo utilizar o Vert.x em conjunto com outros frameworks e bibliotecas, como o Spring e o Quarkus!

Outra característica interessante do Vert.x é que ele é poliglota. Por isso, você pode escrever aplicações reativas com o auxílio do Vert.x para rodar sob a JVM com Java, Groovy, Scala, Kotlin, JavaScript, Ruby e Ceylon.

Você pode até mesmo escrever partes de uma aplicação com cada uma destas linguagens: no final, todo o código rodará sob a JVM e poderá conversar entre si. 

Conceitos essenciais do Vert.x

Aplicações que utilizam o Vert.x são compostas por unidades de processamento que são chamadas de verticles. Os verticles podem ser implantados a partir de uma instância do Vert.x. Os diferentes verticles também são acionados através de uma thread dedicada a repassar eventos para os respetivos verticles: esta thread é chamada de event loop.

vertezz

Talvez, você já tenha visto essa história de event loop em outro lugar, mais especificamente no Node.js. E, de fato, a ideia do Vert.x é exatamente a mesma! Mas, diferentemente do Node.js, onde o event loop fica centralizado em uma única thread; o Vert.x trabalha com múltiplas threads que fazem o papel de múltiplos event loops.

Geralmente, a quantidade de event loops está diretamente relacionada com o número de cores do processador da máquina, embora isso possa ser configurado. Isso torna o Vert.x altamente escalável, tanto para tarefas com alto fluxo de I/O, como para tarefas que exigem processamento mais pesado.

Este padrão de múltiplas threads como event loops é conhecido como Multi-Reactor Pattern. Já o Node.js implementa o Reactor Pattern.

Vert.x e microserviços: uma combinação perfeita

O Vert.x é um toolkit completamente modular. Isso quer dizer que, para que você possa utilizar as funcionalidades básicas do Vert.x, basta importar o core do Vert.x em seu projeto.

Se você precisa utilizar o Vert.x para construir aplicações que lidem com o protocolo HTTP, basta adicionar ao projeto a extensão Vert.x Web. Se você precisa suportar OAuth2, basta adicionar a extensão para OAuth2, e assim por diante.

Essa modularização faz com que aplicações escritas utilizando o Vert.x também sejam extremamente modulares e leves, o que é ideal para o cenário de microserviços, onde a elasticidade precisa ser realizada de maneira mais rápida possível.

Além disso, o Vert.x possui naturalmente extensões com implementações de patterns comuns em arquiteturas orientadas a microserviços, como Service Discovery, Circuit Breaker, Health Checking e Distributed Configuration. Isso certamente faz com que o Vert.x venha a ser uma opção bem interessante caso seu objetivo seja criar microserviços leves, elásticos e reativos.

Nos próximos artigos dessa série, iremos iniciar a implementação de um microserviço básico utilizando o Vert.x. Até mais!

Cleber Lopes Campomori

Cleber Lopes Campomori

Cleber Campomori atua como engenheiro de software sênior na MovilePay. É graduado pela Faculdade de Tecnologia (FATEC) como Tecnólogo em Bancos de Dados e pós-graduado em Projeto e Desenvolvimento de Aplicações Web. Também é profissional MCSD (Microsoft Certified Solutions Developer) na trilha Web e MS Specialist em HTML5, CSS3 e JavaScript. Já atuou em projetos de grandes empresas e organizações como Claro, TIM, Vivo, Multiplus, Instituto Nacional de Pesquisas Espaciais (INPE) e Johnson & Johnson, além de ter ministrado treinamentos técnicos para instituições como DCTA, INPE e Ministério Público do Espírito Santo.

Deixe um comentário

Categorias

Posts relacionados

Siga-nos

Baixe nosso e-book!

%d blogueiros gostam disto: