Aplicación reactiva con WebFlux y Spring Data Reactive

spring-boot-reactive

spring-boot-reactive


En este artículo aplicación reactiva con WebFlux y Spring Data Reactive, vamos a ver como realizar una aplicación en Spring Boot totalmente reactiva haciendo uso de WebFlux que apareció con Spring 5 y las capacidades reactivas que nos ofrece Spring Data.

Para poder realizar esta aplicación, tenemos que tener en cuenta que no solo queremos que sea reactivo el API, sino también, la conexión a nuestra Base de Datos Relacional.

El objetivo de este artículo es únicamente la creación de una aplicación reactiva con WebFlux y Spring Data Reactive, si quieres algo más de información sobre programación reactiva y WebFlux, puedes echar un ojo a este artículo.

Antes de ver el ejemplo vamos a ver los objetos y clases para crear una aplicación reactiva.

Clases para crear aplicación Reactiva en Spring Boot con Spring Data Reactive

Para crear aplicaciones reactivas, se hace uso de una serie de interfaces funcionales para enrutar y gestionar las peticiones, y unos repositorios para hacer reactiva nuesta comunicación con Base de Datos.

Estas clases se encuentran dentro del paquete reactivo de Spring Boot.

Al igual que hacemos con un @Controller o @RestController en una aplicación con Spring Boot para enrutar las llamadas, vamos a hacerlo con Handler y Router. Además igual que hacemos con nuestra capa repository con Spring Data (extendemos de JPARepository por ejemplo) vamos a extender de ReactiveSortingRepository para hacer la conexión a BBDD reactiva.

HandlerFunction

Esta interfaz funcional (@FunctionalInterface), es una función que crea las respuestas en función de las solicitudes que se le envían.

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
  Mono<T> handle(ServerRequest var1);
}

RouterFunction

En el RouterFunction, se encuentra la lógica de enrutamiento del API de una aplicación reactiva. Es la alternativa a una aplicación imperativa con @RequestMapping.

@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
  Mono<HandlerFunction<T>> route(ServerRequest var1);

  default RouterFunction<T> and(RouterFunction<T> other) {
    return new SameComposedRouterFunction(this, other);
  }
.....

La mejor manera de crear rutas en nuestra aplicación será haciendo uso del método route() que se encuentra dentro de la clase RouterFunctions. Además, gracias a que route(), devuelve un objeto RouterFunction, podemos anidar diferentes rutas:

@Bean
RouterFunction<ServerResponse> chainRoutes() {
  return 
    route(GET("/cars"), 
      req -> ok().body(
        carRepository().findAllCars(), Car.class))
        
    .and(route(GET("/cars/{id}"), 
      req -> ok().body(
        carRepository().findCarById(req.pathVariable("id")), Car.class)));
}

R2dbcRepository

Esta es la clase encargada de crear una conexión reactiva con nuestra Base de Datos, R2dbcRepository se encuentra dentro del paquete reactive de Spring Data.

Esta clase obtiene los mismos beneficios que con Spring Data de manera imperativa, como por ejemplo, los métodos save, findById, deleteByID, la ordenación etc…

Para poder hacer uso de este repositorio tan solo es necesario extender de el, y Spring Data se encargará del resto. Hay que tener en cuenta que nuestra Base de Datos también debe de funcionar de manera reactiva.

Nuestra Base de Datos también tiene que funcionar de manera reactiva

public interface CarRepository extends ReactiveSortingRepository<Car, Long> {

}

Como podemos ver el funcionamiento es el mismo que por ejemplo con CrudRepository.

Manos a la obra para crear aplicación reactiva con WebFlux y H2

Para poder realizar nuestra aplicación totalmente reactiva, vamos a comenzar añadiendo las dependencias que necesitamos.

Por un lado habrá que añadir las dependencias que hacen reactiva la interacción con nuestra aplicación através de webflux, y por otro lado las dependencias que harán reactiva nuestra aplicación con la base de datos. Hay que tener en cuenta que si nuestra API es Reactiva pero nuestra Base de Datos no lo es, al final no tendremos una aplicación reactiva. Por lo que se añadirá una dependencia de Spring Data totalmente reactiva.

Si nuestra API es Reactiva pero nuestra Base de Datos no lo es, al final no tendremos una aplicación reactiva

Nuestro ejemplo es un Crud muy básico con una única tabla Car, pero en donde podemos ver la integración con los módulos reactivos de Spring. Para el ejemplo vamos a hacer uso de una Base de Datos en memoria reactiva H2.

La estructura de paquetes se estructura de manera funcional con una estructura clásica de routes, services, repository, entity y configuration. Si quieres optar por una estructura por ejemplo Hexagonal, te recomiendo este enlace.

Dependencias Maven en una aplicación reactiva con Spring Boot

Dependencia webflux:

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

Dependencia reactivas de Spring Data y H2:

    <dependency>
      <artifactId>spring-data-r2dbc</artifactId>
      <groupId>org.springframework.data</groupId>
    </dependency>
    <dependency>
      <artifactId>r2dbc-h2</artifactId>
      <groupId>io.r2dbc</groupId>
    </dependency>

Creación de un Controlador Reactivo con WebFlux

WebFlux es el módulo de Spring utilizado para crear una aplicación Reactiva. Esta dependencia nos permitirá crear nuestros endpoints y las llamadas a otras clases de manera reactiva haciendo uso de los objetos Flux y Mono a través de la librería reactor.

Para crear nuestro controlador reactivo vamos a hacer uso de dos diferentes clases, una clase Handler y una clase Routes. La primera, Handler, será la encargada de añadir la lógica obteniendo los valores de la request y gestionar las llamadas de nuestra clase Routes, que será la que tenga el API con los endpoints.

Para poder realizar las llamadas nos basaremos en la clase RouterFunction de Spring, que como hemos comentado antes, sirve como alternativa al conocido RequestMapping.

Vamos a crear nuestra función de rutas haciendo uso de RouterFunction:

@Configuration
public class CarRoutes {

  @Bean
  public RouterFunction<ServerResponse> routes(CarHandler handler) {
    return route().path(
        "/car", builder -> builder
            .GET("", handler::getAll)
            .GET("/{id}", handler::getOne)
            .POST("",handler::save)
            .DELETE("/{id}", handler::deleteById)
    ).build();
  }
}

Ten en cuenta que al hacer un post si añadimos el Id en el body será considerado un Update y si el body va sin Id será un nuevo registro

La clase anterior, a través del método route(), se encargará de enrutar las llamadas a nuestro handler el cual aplicará la lógica necesaria. Como se puede ver tenemos cuatro endpoints que se encuentran anidados.

La siguiente clase será nuestro Handler, en el que recibirá por parámetro la petición a través de la clase ServerRequest.

@RequiredArgsConstructor
@Slf4j
public class CarHandler {

  private final CarService service;

  public Mono<ServerResponse> getAll(ServerRequest req) {
    var all = service.findAll(Sort.by("model", "brand"));
    return ok().body(fromPublisher(all, Car.class));
  }

  public Mono<ServerResponse> getOne(ServerRequest req) {
    var mono = service
        .findById(Long.valueOf(req.pathVariable("id")))
        .switchIfEmpty(Mono.error(() -> new ResponseStatusException(NOT_FOUND)));
    return ok().body(fromPublisher(mono, Car.class));
  }

  public Mono<ServerResponse> save(ServerRequest req) {

    final Mono<Car> car = req.bodyToMono(Car.class);
    return ok()
        .contentType(MediaType.APPLICATION_JSON)
        .body(fromPublisher(car.flatMap(service::save), Car.class));
  }

  public Mono deleteById(ServerRequest req) {

    var id = Long.valueOf(req.pathVariable("id"));
    return ok()
        .contentType(MediaType.APPLICATION_JSON)
        .body(service.deleteById(id), Void.class);
  }
}

La clase anterior será la que aporta toda la lógica necesaria a nuestra clase de routes (CarRoutes), y será la encargada de realizar las llamadas necesarias al servicio para devolver un objeto Mono.

Creación de Servicio Reactivo con WebFlux

Nuestra capa de Service, será una capa intermedia sin ninguna lógica añadida, la cual únicamente se encargar de pasar la información de la clase routes a nuestra clase repository.

Esta clase se compondrá de cuatro métodos, que será los cuatro diferentes endpoints que vamos a tener.

@Slf4j
@RequiredArgsConstructor
public class CarService {

  private final CarRepository carRepository;

  public Mono<Car> findById(Long id) {
    return carRepository
        .findById(id)
        .doOnNext(p -> log.info("Car with id " + p.getId()));
  }

  public Mono<Void> deleteById(Long id) {
    return carRepository.deleteById(id).doOnNext(c -> log.info("Car with id {} deleted", id));
  }

  public Mono<Car> save(Car car) {

    return carRepository.save(car);
  }

  public Flux<Car> findAll(Sort sort) {
    return carRepository.findAll(sort);
  }
}

Como podemos ver, esta clase simplemente llamará a la clase Repository que será la encargada de ofrecernos las operaciones con la Base de Datos.

Creación de Repositorio Reactivo con Spring Data

Al igual que hemos creado unas rutas que tienen nuestros endpoints para que funcionen de manera reactiva, necesitamos que nuestra Base de Datos también funcione de manera reactiva, por lo que hacemos uso de ReactiveSortingRepository de Spring Data, aunque también podríamos usar ReactiveCrudRepository, aunque no nos ofrecería orden ni paginación.

public interface CarRepository extends ReactiveSortingRepository<Car, Long> {

}

Al crear la capa repository haciendo uso de las capacidades reactivas de Spring Data, estamos logrando una aplicación totalmente reactiva no bloqueante.

Conclusión

Cada vez es mucho más extenso y común el trabajar y programar de manera reactiva para evitar las llamadas bloqueantes. Gracias a spring y al módulo reactive hemos podido ver como crear una aplicación reactiva con WebFlux y Spring Data Reactive. Hay que tener en cuenta que en este ejemplo hemos hecho uso de H2 como Base de Datos ya que nos proporciona capacidades reactivas.

Si quieres ver el ejemplo entero puedes echar un ojo en nuestro Github.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com y te ayudaremos encantados!


No te olvides de seguirnos en nuestras redes sociales Facebook o Twitter para estar actualizado.


Deja una respuesta

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