In this article, “Reactive Application with WebFlux and Spring Data Reactive,” we will see how to build a fully reactive Spring Boot application using WebFlux, which was introduced with Spring 5, and the reactive capabilities provided by Spring Data.
To create this application, we need to consider that we not only want the API to be reactive but also the connection to our relational database.
The objective of this article is solely the creation of a reactive application with WebFlux and Spring Data Reactive. If you would like more information about reactive programming and WebFlux, you can take a look at this article.
Before we delve into the example, let’s explore the objects and classes required to create a reactive application.
Classes for Creating a Reactive Application in Spring Boot with Spring Data Reactive
To create reactive applications, we make use of a set of functional interfaces for routing and handling requests, as well as repositories for making our communication with the database reactive.
These classes are located within the reactive package of Spring Boot.
Just as we use @Controller or @RestController in a Spring Boot application to route calls, we will do the same with Handler and Router. Similarly, as we do with our repository layer using Spring Data (extending from JPARepository, for example), we will extend ReactiveSortingRepository to establish a reactive database connection.
HandlerFunction
This functional interface (@FunctionalInterface) is a function that generates responses based on the requests it receives.
@FunctionalInterface public interface HandlerFunction<T extends ServerResponse> { Mono<T> handle(ServerRequest var1); }
RouterFunction
In the RouterFunction, you’ll find the routing logic for the API of a reactive application. It serves as an alternative to an imperative application with @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); } .....
The best way to create routes in our application is by using the route() method found within the RouterFunctions class. Furthermore, because route() returns a RouterFunction object, we can nest different routes:
@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
This is the class responsible for establishing a reactive connection with our database. R2dbcRepository is located within the reactive package of Spring Data.
This class inherits the same benefits as Spring Data but in a reactive manner, including methods like save, findById, deleteByID, sorting, etc.
To use this repository, all you need to do is extend it, and Spring Data will take care of the rest. It’s important to note that our database must also operate reactively.
Our database must also operate reactively.
public interface CarRepository extends ReactiveSortingRepository<Car, Long> { }
As we can see, the operation is the same as, for example, with CrudRepository.
Let’s get to work to create a reactive application with WebFlux and H2.
To create our fully reactive application, we’ll start by adding the necessary dependencies.
We require dependencies for interaction through WebFlux and for database reactivity in our application. It’s important to note that if our API is reactive but our database isn’t, we won’t have a fully reactive application. Therefore, we’ll include a Spring Data dependency that is fully reactive.
If our API is reactive but our database isn’t, we won’t have a fully reactive application in the end.
Our example is a very basic CRUD operation with a single “Car” table, but it demonstrates the integration with Spring’s reactive modules. For this example, we’ll use an in-memory reactive H2 database.
The package structure is organized in a functional manner with a classic structure of routes, services, repository, entity, and configuration. If you prefer use a Hexagonal architecture you can take a look here.
Maven Dependencies in a Reactive Application with Spring Boot
Webflux dependency:
<dependency> <artifactId>spring-boot-starter-webflux</artifactId> <groupId>org.springframework.boot</groupId> </dependency>
Reactive Spring Data and H2 Dependency:
<dependency> <artifactId>spring-data-r2dbc</artifactId> <groupId>org.springframework.data</groupId> </dependency>
<dependency> <artifactId>r2dbc-h2</artifactId> <groupId>io.r2dbc</groupId> </dependency>
Creating a Reactive Controller with WebFlux
WebFlux is the Spring module used to create a Reactive application. This dependency allows us to create our endpoints and make calls to other classes in a reactive manner, using Flux and Mono objects through the reactor library.
To create our reactive controller, we will use two different classes: a Handler class and a Routes class. The Handler class will be responsible for adding logic to retrieve request values and manage calls to our Routes class, which will contain the API with its endpoints.
We’ll use the Spring RouterFunction as an alternative to RequestMapping for these calls.
Let’s create our route function using 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(); } }
Please note that when making a POST request, if you include the ID in the request body, it will be considered an update, while if the body does not contain an ID, it will be treated as a new record.
The previous class, using the route() method, will handle routing calls to our handler, which will apply the necessary logic. As you can see, we have four nested endpoints.
The next class will be our Handler, which will receive the request as a parameter through the ServerRequest class.
@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); } }
The previous class will provide all the necessary logic to our routes class (CarRoutes) and will be responsible for making the necessary calls to the service to return a Mono object.
Creating a Reactive Service with WebFlux
Our Service layer will be an intermediate layer without any added logic, responsible solely for passing information from the routes class to our repository class.
This class will consist of four methods, corresponding to the four different endpoints we will have.
@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); } }
As we can see, this class will simply call the Repository class, which will be responsible for providing us with the database operations.
Creating a Reactive Repository with Spring Data
Just as we have created routes that have our endpoints to work reactively, we need our database to also operate reactively. Therefore, we use Spring Data’s ReactiveSortingRepository, although we could also use ReactiveCrudRepository, although it wouldn’t provide sorting or pagination.
public interface CarRepository extends ReactiveSortingRepository<Car, Long> { }
By creating the repository layer using the reactive capabilities of Spring Data, we are achieving a fully reactive, non-blocking application.
Conclusion
Working and programming reactively to avoid blocking calls is becoming increasingly extensive and common. Thanks to Spring and the reactive module, we have seen how to create a reactive application with WebFlux and Spring Data Reactive. It’s important to note that in this example, we have used H2 as the database since it provides reactive capabilities.
If you want to see the complete example, you can take a look at our Github.