Guía de WebFlux en Spring Boot


WebFlux nace en Spring 5, como un stack para dotar a Spring de funcionalidades non-blocking y programación reactiva. En esta entrada sobre guía de WebFlux en Spring Boot vamos a ver los conceptos básicos y como aplicarlos sobre programación reactiva y WebFlux.

Con Spring 5, también se introdujó WebClient que funciona gracias a WebFlux de manera reactiva, si quieres ver más puedes echar un vistazo a este artículo sobre WebClient.

¿Qué es la Programación Reactiva?

La programación reactiva es un paradigma de la programación que se basa en los Streams y en la propagación del cambio. Y esto qué quiere decir? , básicamente que podrás crear desde cualquier sitio y para cualquier cosa un stream, como en eventos, peticiones HTTP, etc…, y esos streams que creas provocarán un cambio de estado. Y esto de manera indirecta hace que la información se procese de manera asíncrona.

¿Por qué usar Programación Reactiva?

Actualmente, nuestros modelos de desarrollo se están orientando, sino lo están ya, hacia una arquitectura de microservicios. Este paradigma al final busca un mayor desacople de los componentes, que puede ser logrado haciendo uso de Programación Reactiva.

Con la Programación Reactiva se facilita la escabilidad horizontal de nuestra arquitectura. Y hará nuestro sistema mucho más resiliente permitiendo aislar los fallos que puedan ocurrir.

Además hará nuestra arquitectura Responsiva. Con esto queremos decir que los problemas y los errores son detectados a tiempo, en un tiempo limitado. De manera que aseguramos consistencia a nuestra infraestructura.

Vamos a ver dos conceptos que son muy importantes dentro de la programación reactiva procesamiento bloqueante y procesamiento no bloqueante o más conocido en inglés como Non-blocking.

Procesamiento bloqueante

En este tipo de procesamiento, cuando una petición es realizada, es gestionada por un thread. Esté, delega la petición a los threads para operaciones I/O como accesos a bases de datos.

Mientrás se realiza la petición a Base de Datos, estos threads estarán ocupados esperando una respuesta y estarán bloqueados. Esta es la típica programación síncrona. En este caso, el número de threads o hilos, será el que limite el número de peticiones.

Procesamiento no bloqueante

Las peticiones se delegan a un thread pool, que inmediatamente delega a un handler de manera que el thread pool pueda seguir recibiendo peticiones. El «handler» que se encuentra gestionando la petición, cuando la termina, llamará a un thread del pool que a su vez llamará a una función para devolver la respuesta.

Al final esto se traduce en un ahorro de threads, ya que un menor número de threads, serán capaces de procesar un mayor número de peticiones con un menor número de recursos.

Programación Reactiva con Spring Boot

Spring WebFlux fue construido como un projecto más dentro de todos los de Spring, para dotar al framework de programación reactiva. Para ello se ha apoyado en Netty. El cual es un servidor – cliente no bloqueante (non-blocking I/O) para el desarrollo de aplicaciones Java.

WebFlux se ha apoyado, además, en el proyecto reactor como librería reactiva. Esta librería que se ha desarrollado junto con Spring, nos proporciona los Mono y los Flux que a través de su API, nos permitirá trabajar con un conjunto de operadores alineados con el standard de ReactiveX.

A continuación vamos a crear una pequeña aplicación para ver cómo funciona WebFlux.

Ejemplo de Spring Boot con WebFlux

Antes de comenzar, puedes bajar el ejemplo completo de nuestro repositorio en github.

A continuación vamos a crear una aplicación con WebFlux para ellos vamos a seguir los siguientes pasos:

  • Creación del pom.xml con las dependencias de webflux.
  • Creación de un dominio Coche.
  • RestController haciendo uso de WebFlux.
  • Creación de un repositorio también reactivo.

Dependencias Maven en un proyecto con WebFlux

Haciendo uso del initializr de Spring vamos a seleccionar la dependencia de Spring Reactive Web que nos dará la funcionalidad para webflux y aparte vamos añadir la dependencia de mongo reactive.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
     <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

Creación del Dominio

Vamos a crear un dominio coche con tres campos, id, brand y color.

@Getter
@Setter
@Document
@AllArgsConstructor
@ToString
public class Car {

    @Id
    private Long id;

    private String brand;

    private String color;

}

Creación del controlador

Como hemos comentado anteriormente WebFlux nos va a proporcionar los Mono y los Flux.

Ambos componentes forman parte del stack reactivo, pero nos van a proporcionar diferentes características:

Mono: Este tipo nos va a devolver 0 o 1 elemento.

Flux: Devuelve de 0 a N elementos. Returns 0…N elements. Un flujo de streams puede ser infinito, lo que quiere decir, que se puede mantener emitiendo items para siempre.

Por lo tanto en nuestro controlador vamos a tener dos métodos uno Mono y otro Flux. El método Mono nos devolverá un único objeto mientrás flux nos devolverá una lista.

    @GetMapping("/{id}")
    public Mono<Car> getById(@PathVariable Long id) {
        return carRepository.findById(id);
    }
    @GetMapping()
    public Flux<Car> getCars() {
        return carRepository.findAll().delayElements(Duration.ofMillis(DELAY_PER_ITEM_MS));
    }

Creación de Repositorio Reactivo

Para obtener todos los beneficios de WebFlux debemos de tener un stack completamente reactivo, por lo que vamos a establecer nuestra conexión con una Base de Datos MongoDb de manera también reactiva.

Todo nuestro sistema deberá ser capaz de trabajar de manera reactiva para obtener el mejor rendimiento

Para realizar la conexión en base de datos de manera reactiva vamos a extender de la siguiente clase ReactiveSortingRepository. Esta clase añade las características de un CRUD y además las funcionalidades reactivas. Y todas las características propias de Spring Data.

public interface CarMongoReactiveRepository extends ReactiveSortingRepository<Car, Long> {

}

Arrancar MongoDB con Docker

Vamos a hacer uso de Docker para arrancar nuestra Base de Datos Mongo para ello ejecutaremos el siguiente docker compose:

version: "2"

services:
  mongo:
    image: mongo:latest
    hostname: mongo
    ports:
      - "27017:27017"
    volumes:
      - mongodata:/data/db

volumes:
  mongodata:

Para arrancarlo ejecutaremos:

docker-compose up -d

Cargando datos en nuestra aplicación

Vamos a empezar cargando datos en nuestra aplicación para poder hacer uso de nuestra aplicación de forma reactiva, para ello vamos a arrancar la aplicación:

Vamos a hacer uso de la clase de Spring ApplicationRunner para hacer una carga de datos en nuestra base de datos:

public class Configuration implements CommandLineRunner {

    private final CarRepository carRepository;

    Long l = 0L;

    @Override
    public void run(String... args) throws Exception {

        if (carRepository.count().block() == 0) {

            var idSupplier = getIdSequenceSupplier();
            
            var cars = List.of(new Car(idSupplier.get(), "Ford", "Yellow"),
                    new Car(idSupplier.get(), "Ford", "Yellow"),
                    new Car(idSupplier.get(), "RENAULT", "Yellow")
                    // añadir más                   .........
            );


            carRepository.saveAll(cars).subscribe(car->log.info("Car saved {} ", car.toString()));

        }

    private Supplier<Long> getIdSequenceSupplier() {
        return () -> l++;
    };
}

CommandLineRunner es una interfaz funcional que se ejecutará justo antes de que la aplicación de Spring Boot se arranque. Por lo que para este caso en el que necesitamos popular nuestra base de datos, justo antes de que la aplicación se encuentre arrancada es un ejemplo perfecto.

Arrancando y probando nuestra aplicación reactiva

Una vez hemos hecho todos los pasos, vamos a arrancar nuestra aplicación mediante:

spring-boot:run 

Tenemos dos maneras de probar nuestra aplicación de la manera clásica haciendo peticiones y obteniendo el resultado, que sería bloqueante, o a la manera reactiva, vamos a ver ambas:

Manera bloqueante: Vamos a hacer una llamada a nuestro endpoint, haciendo uso de postman o mediante un curl desde un terminal:

curl "http://localhost:8080/cars"

Vemos que al realizar la llamada, se devuelve todos los elementos de golpe, es decir, no vemos diferencia entre usar webflux o no usarlo. Esta funcionando de manera bloqueante. Al no indicar ningún formato de respuesta, por defecto es JSON. Y como tenemos 90 elementos y un delay de 100 ms por elemento tardaremos unos 9 segundos en devolver todos los elementos de golpe.

Manera reactiva: Ahora vamos verlo funcionando de manera reactiva, vamos a indicar que la respuesta sea text/event-stream, es decir, Server-Sent Events (SSE). Para ello ejecutamos el siguiente comando por el terminal:

curl -H "Accept: text/event-stream" "http://localhost:8080/cars"

Como se puede ver ahora la respuesta es diferente, se esta enviando información en secuencia cada vez que se va recuperando de nuestra base de datos. Siempre y cuando nuestra base de datos también pueda trabajar de manera reactiva. Los elementos se irán devolviendo en secuencia cada 100 ms.

Curl with event-stream | Guía de WebFlux en Spring Boot
Curl with event-stream

Conclusión

En esta entrada sobre Guía de WebFlux en Spring Boot, hemos visto como el uso de la programación reactiva puede llegar a mejorar el rendimiento de nuestras aplicaciones y evitar el backpressure. Hay que tener en cuenta que el funcionamiento será optimo si todo nuestro sistema es capaz de funcionar con programación reactiva también.

Puedes encontrar el ejemplo completo en nuestro github.

Si te ha gustado no dudes en poner un comentario así como si tienes alguna duda.

Síguenos en nuestras redes sociales a través de Facebook o Twitter para estar al día de nuestros artículos.


1 pensamiento sobre “Guía de WebFlux en Spring Boot

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *