Utilizando CompletableFuture para fazer requisições assíncronas em uma aplicação Spring Cloud

Com um mundo cada vez mais demandando velocidade e usuários cada vez mais exigentes, surge a necessidade de realizar otimizações em aplicações rest para o tempo de requisição ser o menor possível.

Imagine um cenário onde você possui um orquestrador e para devolver a resposta para seu usuário, você precisa realizar outras requisições HTTP em outros microsserviços,

Para demonstrar como realizar requisições assíncronas no Spring Cloud utilizando o CompletableFuture do Java 8, será criado um projeto orquestrador que retornará os dados de um usuário, e para agrupar os dados desse usuário, será necessário fazer três requisições para três REST APIs diferentes. o intuito será mostrar como realizar essas requisições simultaneamente e não sequenciais, diminuindo assim o tempo de resposta da API.

Exemplo prático

A aplicação principal será uma aplicação spring cloud criada no site https://start.spring.io como mostrado na figura 1:

Figura 1: Configuração utilizada no microserviço orquestrador

Será utilizado a dependencia OpenFeign para realizar as requisições Http para os microsserviços secundários e o lombok para facilitar a criação das classes Java.

Criação dos microsserviços que serão consumidos

Para simular os serviços a serem consumidos, foi feito três scripts utilizando Node.js com o framework Express.js, e para roda-los simultaneamente, foi utilizado um script para rodar três processos do Node para não ter nenhuma interferência entre os serviços.

A figura 2 mostra respectivamente os scripts do serviço um, que será utilizado para buscar informações de musicas do usuário, serviço dois que será utilizado para buscar informações gerais e serviço três que será utilizado para buscar informações de postagens desse usuário. Logo abaixo, existe o script bash para rodar os servers simultaneamente e logo ao lado, a declaração de uma função sleepOneSecond que será utilizada para simular um delay em um banco de dados fictício, o que não existe no exemplo pois os dados estão mockados.

Figura 2 — Estrutura dos serviços em Node que serão consumidos.

Preparando o orquestrador

Com o projeto criado no Spring Initializr, será criado primeiramente algumas classes para preenchimento dos dados que serão recebidos pela api, como pode ser visto na figura 3:

Figura 3 — POJOs que serão utilizadas no projeto, a classe UserResponse será utilizada como a classe de saída do endpoint, já as demais serão as classes que serão preenchidas ao realizar as requisições.

Também será criado uma camada de services e clients na aplicação, que ficará responsável principalmente pela responsabilidade de realizar requisições HTTP e retornar um objeto para quem estiver consumindo a camada de service, bem semelhante o que existe no framework frontend Angular, a implementação pode ser vista na figura 4:

Figura 4 — Classes responsáveis por realizar as requisições HTTP, note que cada endpoint que será chamado tem sua própria camada de service e client.

Feito isso, agora será estruturado o controller da aplicação, a estrutura do método principal conterá as três chamadas para os serviços, logo em seguida, será montado o objeto final do usuário utilizando o padrão de projeto Builder, já implementado pelo plugin Lombok, a figura 5 demonstra como ficou o resultado final da classe:

Figura 5 — Controller principal da aplicação

Não se esqueça de colocar a annotation @EnableFeignClients logo acima da classe main do seu projeto, para a injeção de dependencia @Autowired do Spring funcionar corretamente.

Observe que também foi colocado um println para mostrar no console quanto tempo durou a requisição, agora subindo a aplicação e enviando uma requisição pelo postman, verificamos que está tudo funcionando (lembre-se de subir as aplicações em Node também!) , como mostra a figura 6:

Figura 6 — Resposta da API funcionando, i see this as an absolute win… right?

Perceba que os dados foram retornados corretamente, porém perceba que o tempo da requisição ficou cerca de quatro segundos, o que, dependendo do caso de uso, pode ser bem lento.

Isto ocorre pois as requisições HTTP estão acontecendo uma atrás da outra, e se fosse possível realizar todas as requisições ao mesmo tempo e só obter as respostas no momento de montar o objeto de saída da aplicação? isto que será feito a seguir!

A versão 8 do Java possui uma API muito simples de ser utilizada chamada CompletableFuture, com ela, é possível solucionar o problema visto anteriormente com bastante facilidade, para demonstrar como é utilizado, será criado um novo método no controller só que desta vez, utilizado requisições assíncronas, como visto na figura 7:

Figura 7 — Novo método utilizando a API do CompletableFuture.

A principal diferença do novo método para o antigo é a de que utilizamos o CompletableFuture.supplyAsync(Supplier supplier) e passamos como argumento um supplier retornando a função no qual é desejado o retorno, no caso, é feito isso nas três chamadas HTTP, o que acontece por debaixo dos panos é que cada supplyAsync cria uma nova thread para rodar a requisição paralelamente com a thread principal da aplicação.

Outra principal diferença é que o objeto que retornará das requisições é do tipo CompletableFuture, isto acontece pois ainda não existe esse objeto em memória, já que ele será obtido no futuro, então as linhas 61 até 66 irão ser rodadas ao mesmo tempo sem uma bloquear a outra.

Vale apontar também que os métodos do service precisam ter a annotation @Async para sinalizar para o Spring que o método é assíncrono, para mais detalhes, consultar o segundo link que está nas referencias no final do artigo.

A aplicação só será bloqueante novamente quando é feito um .get() no objeto de retorno, pois assim é sinalizado para o Java que a aplicação não pode continuar a partir daquele ponto sem os dados retornados da API.

Com as alterações feitas, será feito então uma nova requisição pelo postman só que pelo novo endpoint, como mostra a figura 8:

Figura 8 — Requisição feita no novo endpoint usando programação assíncrona, observe que durou 1/3 do tempo original da requisição.

Com isso, é possível observar o ganho obtido utilizando programação assíncrona com CompletableFuture, que é uma API que já está presente no Java 8 ou versões mais novas.

Vou estar deixado meu Linkedin aqui no post caso alguém tenha alguma duvida, critica ou sugestão a respeito do artigo, eu ficaria bem feliz pelo feedback! também estarei deixando o repositório da aplicação criada, junto com os scripts em Node utilizados para simular os microsserviços

Until then, stay safe!

Marco Antonio G. - FATEC Americana - Campinas, São Paulo, Brazil | LinkedIn

View Marco Antonio G.’s profile on LinkedIn, the world’s largest professional community. Marco Antonio’s education is…

www.linkedin.com

marcoagpegoraro/spring-cloud-requisicoes-assincronas

Contribute to marcoagpegoraro/spring-cloud-requisicoes-assincronas development by creating an account on GitHub.

github.com

Referências

CompletableFuture (Java Platform SE 8 )

supplyAsync Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor…

docs.oracle.com

Creating Asynchronous Methods

This guide walks you through creating asynchronous queries to GitHub. The focus is on the asynchronous part, a feature…

spring.io

 

Marco Pegoraro

Hello, my name is Marco, welcome to my personal blog, here i write posts about tecnology, programming, life, tech reviews among other things.


By Marco Pegoraro, 2020-08-22