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
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!