DEV Community

saudade de liberdade
saudade de liberdade

Posted on

Consumindo apis externas com spring webflux

Há algum tempo ajudando uma amiga com um teste para um processo seletivo, me deparei com o desafio de consumir apis externas. Desafio dado, porque não buscar algo novo e aprender algo diferente? Foi assim que escolhemos o webclient do Spring Webflux para a tarefa.

Segundo a documentação do Spring, o Webflux é a stack reativa do Spring que foi adicionado a partir da versão 5 do framework que está presente na versão 2.0 ou superiores do Springboot. Com ele é possível fazer chamadas síncronas e assíncronas e tem sido utilizado em muitos projetos no mercado, inclusive no que eu trabalho atualmente! :)

Neste projeto vamos consumir apis do star wars a ideia é criar uma api de leilão para os veículos da franquia. Num primeiro momento vamos acessar os endpoints que retornam todos os veículos e os que devolvem os veículos individualmente.
Partiu?

Vamos começar entendendo como disponibilizar essa ferramenta na nossa api. Eu prefiro utilizar o gradle como ferramenta de build e ele foi o escolhido para esse projeto, outro ponto importante, foi que utilizei o spring initializr para criar o projeto, sendo assim foi preciso apenas adicionar a dependência do Spring Reactive Web. Caso prefira criar o projeto de outra maneira, pode adicionar a seguinte linha:
implementation 'org.springframework.boot:spring-boot-starter-webflux'
ao arquivo build.gradle.

Com o webflux disponível, podemos criar nosso webclient que é quem vai orquestrar as nossas requisições a star wars api. Como se trata de uma biblioteca externa precisamos que o Spring possa tomar conta do seu ciclo de vida pra nós, e para isso a transformamos em um bean. Isso pode ser feito na classe que está anotada com o @SpringBootApplication como abaixo:

@SpringBootApplication
public class MyApplication {
  @Bean
  public WebClient webClient(WebClient.Builder builder) {
     return builder.baseUrl("https://swapi.dev/api/")
           .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
           .build();
  }
  public static void main(String[] args) {
     SpringApplication.run(MyApplication.class, args);
  }

}

Ou ainda criar uma classe de onde possamos configurar todos os beans da nossa aplicação, mantendo assim a classe com uma única responsabilidade, o que eu prefiro fazer. A minha ficou assim:

@Configuration
public class BeanConfiguration {

   private String starWarsUrl;

   public BeanConfiguration(StarWarsConfiguration starWarsConfiguration){
       this.starWarsUrl = starWarsConfiguration.starWarsBaseUrl;
   }

   @Bean
   public WebClient webClient(WebClient.Builder builder) {
       return builder.baseUrl(starWarsUrl)
               .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
               .build();
   }
}

Outra boa prática que gosto de seguir, é não manter informações que são dinâmicas 'chumbadas' no código, como é o caso da url base da api que queremos acessar. Para manter essas informações isoladas em um único local preferi guardar os dados no application.properties do meu projeto:

starwars.base-url = https://swapi.dev/api/
starwars.vehicles = vehicles/
starwars.vehicle = vehicles/{id}/

E para acessar esses dados, usei mais uma classe de configuração. Aqui, a única função dela é disponibilizar as informações do application.properties em variáveis.

@Configuration
@PropertySource("classpath:application.properties")
public class StarWarsConfiguration {

   @Value("${starwars.base-url}")
   public String starWarsBaseUrl;

   @Value("${starwars.vehicles}")
   public String starWarsVehicles;

   @Value("${starwars.vehicle}")
   public String starWarsVehicle;
}

Com todas as classes de configurações apresentadas, deixo uma imagem para que fique um pouco mais claro como elas se conversam.

Alt Text

Partindo para as nossas requisições, a ideia foi criar um component cuja função seja apenas isso, acessar a api do star wars e retornar as informações que precisamos. Primeiro, disponibilizo através do construtor o bean do webclient para utilizar nas chamadas e as variáveis que contem as urls que serão buscadas.

@Component
public class StarWarsWebClient {

   private final String allVehiclesResource;
   private final String vehicleResource;
   private WebClient webClient;

   StarWarsWebClient(WebClient webClient, StarWarsConfiguration starWarsConfiguration) {
       this.webClient = webClient;
       this.allVehiclesResource = starWarsConfiguration.starWarsVehicles;
       this.vehicleResource = starWarsConfiguration.starWarsVehicle;
   }

Feito isso é só criar o método que efetivamente vai fazer a requisição. Neste caso buscaremos primeiro todos os veículos e aqui é preciso fazer uma ressalva, se a api externa retornasse apenas uma lista com todos veículos disponíveis [{},{}], poderíamos utilizar um flux e o método ficaria mais ou menos assim:

public Flux<VehicleResponse> getAllVehicles() {
        Flux<VehicleResponse> response = webClient
.get() // metodo http utilizado na requisicao
                .uri(allVehiclesResource) //recurso a ser atingido encapsulado na variavel
                .retrieve() //executa a chamada
                .bodyToFlux(VehicleResponse.class); //transforma a resposta em um Flux do tipo VehicleResponse
        return response;
    }

No entanto a nossa api retorna um único objeto contendo em uma de suas propriedades uma lista de veículos {[]}, nesse caso o método utilizado foi o método a seguir, e por enquanto focaremos só no Mono:

public AllVehicleResponse getAllVehicles() {
   return webClient
           .get()
           .uri(allVehiclesResource)
           .retrieve()
           .bodyToMono(AllVehicleResponse.class)
           .block();
}

E para atingir os veículos individualmente, o processo é quase o mesmo:

public VehicleResponse getVehicle(Integer id) {
   return webClient
           .method(HttpMethod.GET)
           .uri(vehicleResource, id)
           .retrieve()
           .bodyToMono(VehicleResponse.class)
           .block();
}

A diferença está na chamada da uri, a variável vehicleResource contem o recurso seguido do id que está sendo buscado vehicles/{id}/ e na frente dele, uma variável que de fato armazena esse id.

A partir daí qualquer classe service no nosso projeto pode acessar os caras que fazem essas requisições e trabalhar com seus dados. Como no exemplo abaixo:

@Service
public class VehiclesService {
   private final VehicleRepository vehicleRepository;
   private final StarWarsWebClient starWarsWebClient;

   public VehiclesService(VehicleRepository vehicleRepository, StarWarsWebClient starWarsWebClient) {
       this.vehicleRepository = vehicleRepository;
       this.starWarsWebClient = starWarsWebClient;
   }

   public AllVehicleResponse getVehicles() {
       return starWarsWebClient.getAllVehicles();
   }

   public VehicleResponse getVehicle(Integer id) {
       return starWarsWebClient.getVehicle(id);
   }
}

Fácil né? Nos próximos capítulos vamos explorar mais esses dados e montar nossa api de leilões! Quer ver o projeto no git? O link é esse aqui. Espero que tenham gostado.
Até!

Top comments (4)

Collapse
 
josvieira profile image
Josiene Vieira

Muito bom o artigo.

Collapse
 
andradesampaio profile image
Andrade Sampaio

Parabéns ficou show

Collapse
 
ananeridev profile image
Ana Beatriz

uaaaaau que demais Gle <3

Collapse
 
kamilacode profile image
Kamila Santos Oliveira

Ficou incrível gleice