Categorías
Desarrollo

WebClient, una alternativa a RestTemplate

Spring proporciona la clase RestTemplate con diferentes métodos para poder realizar peticiones HTTP, pero a partir de Spring 5 se implementa una interfaz HTTP basada en programación reactiva que hará que nuestras peticiones sean no bloqueantes y asíncronas.

¿Qué es la programación reactiva?

La programación reactiva es un paradigma orientado a la manipulación e interacción con flujos de datos finitos o infinitos de forma asíncrona. Este paradigma utiliza el patrón de diseño Observer, de tal forma que cuando hay un cambio de estado en un objeto, se produce un evento que notifica e informa de los cambios producidos en el flujo de datos.

Webflux

Webflux es un marco de Spring que utiliza la librería reactiva Project Reactor, permitiendo generar aplicaciones y hacer uso de las diferentes posibilidades que ofrece este tipo de programación. 

Proporciona una interfaz denominada WebClient que permite realizar comunicación web mediante el protocolo HTTP. Esta funcionalidad provee la posibilidad de implementar consultas de forma asíncrona y no bloqueantes.

WebFlux está disponible a partir de Spring 5 o Spring Boot 2, para utilizarlo se debe añadir la siguiente sentencia en el build.gradle.

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

WebClient Vs RestTemplate

Existen diversas diferencias entre estas dos opciones que proporciona Spring, aunque a la hora de elegir una u otra dependerá de las necesidades que tengamos. A continuación se enumeran las principales diferencias:

  • RestTemplate se basa en un modelo de subproceso por solicitud, lo que bloqueará el hilo hasta que reciba una respuesta. En contraposición, WebClient queda pendiente de recibir un evento para desencadenar una acción, lo que permite su ejecución asíncrona sin necesidad de bloquear el hilo.
  • RestTemplate no va a recibir actualizaciones, solo labores de mantenimiento. Es probable que no sea soportado en futuras versiones, mientras que WebClient tiene una funcionalidad más moderna.
  • WebClient presenta un rendimiento más óptimo puesto que RestTemplate al utilizar un subproceso por cada solicitud consume una determinada cantidad de memoria y ciclos de CPU.

¡Vamos al código!

Para crear consultas, se instancia un objeto de WebClient, pudiendo utilizar diferentes opciones.

  • Crear un objeto inicializado con una url.
WebClient webClient = WebClient.create("http://localhost:8080");
  • Generar el objeto utilizando la opción .builder() la que permitirá una personalización más completa.
WebClient webClient = WebClient.builder().baseUrl()
                      .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                      .build();

El método de la petición, se puede indicar mediante la función method() o invocando directamente sus métodos.

webClient.method(HttpMethod.POST);
webClient.get();
webClient.post();
webClient.put();
webClient.delete();

En la instancia del WebClient se indica la base de la url, trás configurar el método, se formará la ruta completa mediante UriBuilder, indicando el endPoint y los diferentes parámetros en caso de que sea necesario.

webClient.post().uri(uriBuilder -> uriBuilder.path("/endPoint-pruebas")
                .queryParam("param", "value").build());

Se pueden añadir diferentes configuraciones, como las cabeceras, cookies, tipos de datos o el cuerpo de la petición.

webClient.post().uri(uriBuilder -> uriBuilder.path("/endPoint-pruebas")
                .queryParam("param", "value").build())
                .header("Authorization", "token")
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(body), Map.class);

Una parte principal de WebFlux es el Publisher, que es el objeto encargado de emitir los eventos a las suscripciones. Existen dos diferentes:

  • Mono, que se utiliza cuando se va a recibir un elemento.
  • Flux, para cuando se recuperen n elementos.

Para poder realizar la consulta es necesario incluir un método que permita extraer la respuesta, se disponen de dos opciones:

  • .retrieve() devuelve una respuesta simplificada, aunque se puede consultar el estado mediante el método onStatus(). Para deserializar el cuerpo de la respuesta se utiliza .bodyToMono() o .bodyToFlux(), de esta forma se obtiene una manera sencilla de recuperar la información de la respuesta.
Mono<String> result = webClient.post().uri(uriBuilder -> uriBuilder.path("/endPoint-pruebas")
                      .queryParam("param", "value").build())
                      .header("Authorization", "token")
                      .accept(MediaType.APPLICATION_JSON)
                      .body(Mono.just(body), Map.class)
                      .retrieve()
                      .onStatus(HttpStatus::isError, response -> {
                         System.out.println(response.statusCode());
                         return Mono.error(new RuntimeException("Error http " + response.statusCode()));
                      })
                      .bodyToMono(String.class);
  • .exchange() permite tener un mayor control, incluyendo en la respuesta el estado y los encabezados, permitiendo manejar la respuesta mediante los métodos .exchangeToMono() o .exchangeToFlux().

El objeto resultante de la configuración anterior, es un publisher que permite crear la suscripción, para realizar la consulta de forma asíncrona, para ello se utiliza el .subscribe(), que recibirá directamente el cuerpo de la respuesta formateado al objeto que se haya indicado en la configuración.

Mono<String> result = webClient.post().uri(uriBuilder -> uriBuilder.path("/endPoint-pruebas")
                      .queryParam("param", "value").build())
                      .header("Authorization", "token")
                      .accept(MediaType.APPLICATION_JSON)
                      .body(Mono.just(body), Map.class)
                      .retrieve()
                      .onStatus(HttpStatus::isError, response -> {
                         System.out.println(response.statusCode());
                         return Mono.error(new RuntimeException("Error http " + response.statusCode()));
                      })
                      .bodyToMono(String.class);

result.subscribe(res -> System.out.println(res));

Esta configuración además, permite realizar la consulta de forma síncrona, en lugar de realizar una suscripción se realiza un bloqueo con .block(), que desbloqueará el hilo en el momento de obtener la respuesta.

String result = webClient.post().uri(uriBuilder -> uriBuilder.path("/endPoint-pruebas")
                .queryParam("param", "value").build())
                .header("Authorization", "token")
                .accept(MediaType.APPLICATION_JSON)
                .body(Mono.just(body), Map.class)
                .retrieve()
                .onStatus(HttpStatus::isError, response -> {
                   System.out.println(response.statusCode());
                   return Mono.error(new RuntimeException("Error http " + response.statusCode()));
                })
.bodyToMono(String.class).block();

Conclusión

WebClient permite una gran flexibilidad de opciones para implementar diferentes soluciones, su tecnología y paradigma están actualizados a las nuevas necesidades de la programación y la migración hacia WebClient, no es compleja puesto que permite la realización de consultas síncronas, por esto poco a poco irá haciendo que restTemplate quede obsoleto.