DEV Community

saudade de liberdade
saudade de liberdade

Posted on

4 2

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