Spring WebFlux is similar to Spring MVC but allows you to use reactive streams and is asynchronous and nonblocking, which, if used correctly, can allow your application to have better performance.
With WebFlux, we can quickly create asynchronous, nonblocking, and event-driven applications using HTTP or WebSocket connections. Spring uses its own Reactive Streams implementation, Reactor1 (with Flux<T> and Mono<T>), in many of its APIs. You can use another implementation within your application, such as RxJava if you choose, but project Reactor has the best integration with WebFlux.
By default, a Spring WebFlux application uses an embedded instance of Netty,2 an asynchronous event-driven application framework, although you can configure it to use an embedded Tomcat, Jetty, or Undertow instead.
In this chapter, we’ll take a look at implementing a full project using Spring Boot, WebFlux, and Reactor with a MongoDB persistence layer.
With Spring WebFlux, we can very easily create a nonblocking, asynchronous application with a backing MongoDB, Redis, or Cassandra database or any relational database with a R2DBC driver implemented.
Getting Started
For the purposes of this chapter, we will create a Gradle-build, Java-based Spring Boot project.
Spring Boot is highly customizable, and you can add whichever “starters” you want for your project (web, mail, freemarker, security, etc.). This makes it as lightweight as possible.
We’re going to create a WebFlux-based project that uses Spring’s Reactor project along with MongoDB3 to create a fully reactive web application.
The code for this project is available on GitHub at adamldavis/humblecode.4
Gradle Plugin
- 1.
The first thing you might notice is the lack of versions specified; Spring Boot provides those for you and ensures that everything is compatible.
- 2.
We specify the groupId and version for the build artifact. We also specify the Java source version as 11. You don’t need to specify the main class. That is determined by Spring Boot through annotations.
- 3.
We include the “webflux” starter to enable Spring’s WebFlux.
- 4.
We’re including project lombok here just to simplify the model classes. Lombok supplies annotations and automatically generates boilerplate code like getters and setters based on which annotations are used on each class.
- 5.
Here we include the spring-data starter for using reactive MongoDB with Reactor integration.
- 6.
Finally, we include “spring-boot-starter-test” and “reactor-test” to use the test support supplied by Spring.
Keep in mind that in order for the back end to be completely reactive, our integration with the database needs to be asynchronous. This is not possible with every type of database. In this case, we are using MongoDB which supports asynchronous operations.
As of writing, Spring Data provides direct reactive integration for Redis, MongoDB, and Cassandra. You can do this by simply switching “mongodb” for the database you want in the “starter” compile dependency. Spring Data also provided support for R2DBC for relational databases (see Chapter 6 for more).
Tasks
The Spring Boot plugin adds several tasks to the build.
To run the project, run “gradle bootRun” (which runs on port 8080 by default). Look at the command-line output to see useful information like which port your application is running on.
When you’re ready to deploy, run “gradle bootRepackage” which builds a fat jar with everything you need to run the full application in one jar.
SpringBootApplication
Later on, we’ll explore adding security to a WebFlux project.
Our Domain Model
For this section, we will be implementing a very simple website with a RESTful API for online learning. Each course will have a price (in cents), a name, and a list of segments.
- 1.
The first two annotations are Lombok annotations. @Data tells Lombok to add getters and setters for every field, equals and hashCode methods, a constructor, and a toString method.5
- 2.
The @Document annotation is the spring-data-mongodb annotation to declare this class represents a MongoDB document.
- 3.
The @Id annotation denotes the id property of this document.
ReactiveMongoRepository
First, we need to create an interface to our back-end database, in this case MongoDB.
Using the spring-boot-starter-data-mongodb-reactive dependency that we included, we can simply create a new interface that extends ReactiveMongoRepository<T,ID>, and (in addition to the methods already on this interface) Spring will generate the code backing any method we define using a standard naming scheme (as we learned about in Chapter 6). By returning Reactor classes, like Flux<T> or Mono<T>, these methods will automatically be reactive.
- 1.
The first generic type is the type this repository stores (Course) and the second is the type of Course’s ID.
- 2.
This method finds all Courses with the names that match the given search String and returns a Flux<Course>.
- 3.
This method finds all Courses with the given name. If we were sure names are unique, we could have used Mono<Course> findByName(String name).
Simply by extending the ReactiveMongoRepository<T,ID> interface, our repository will have tons of useful methods such as findById, insert, and save all returning Reactor types (Mono<T> or Flux<T>).
Controllers
Next, we need to make a basic controller for rendering our view templates.
As the preceding method returns the string “home”, it would render the corresponding view template.
The GetMapping annotation is identical to using @RequestMapping(path="/hello", method = RequestMethod.GET). It’s not reactive yet, we will add that later on in this chapter.
By default, Spring WebFlux uses an embedded Netty instance. Using the embedded container means that container is just another “bean” which makes configuration a lot easier. It can be configured using application.properties and other application configuration files.
Here the code uses a mix of Java 8’s Optional<T> with Reactor. Note that we must call subscribe on a Flux or else it won’t ever execute. We accomplish this here by calling subscribe() with no parameters. Since count() returns a Mono<Long>, we call blockOptional() which will block (wait for the Mono to complete) and then use the given value; if it is zero, we then save three Course objects to the courseRepository. For a refresher on using Flux and Mono, please see Chapter 12.
View Templates
- 1.
First, we call our RESTful API, which we will define later.
- 2.
Since we’re using jQuery, it automatically determines the response is JSON and parses the returned data.
- 3.
Using forEach, we build an HTML list to display each Course with a link to load each Course.
- 4.
We update the DOM to include the list we built.
- 5.
Here we specify the error handling function in case anything goes wrong with the HTTP request.
Although we’re using jQuery here, we could have chosen any JavaScript library/framework. For Spring Boot, JavaScript files should be stored at src/main/resources/static/js.
RESTful API
CourseController.java
Note how we can return Reactor data types like Flux<T> directly from a RestController since we are using WebFlux. This means that every HTTP request will be nonblocking and use Reactor to determine the threads on which to run your operations.
Note that we are calling the repository directly from the controller in this example. In a production system, it is best practice to add a “service” layer in between the controller and repository to hold the business logic.
Now we have the ability to read Courses, but we also need the ability to save and update them.
Since we’re making a RESTful API, we use @PostMapping to handle HTTP POST for saving new entities and @PutMapping to handle PUT for updating.
Note that the insert method returns a Reactor Mono<Course>; you may recall, a Mono<T> can only return zero or one instance or fail with an error.
Note how we use flatMap here to update the course and return the result of the save method which also returns a Mono<T>. If we had used map, the return type would be Mono<Mono<Course>>. By using flatMap, we “flatten” it to just Mono<Course> which is the return type we want here.
For more information on Reactor, see Chapter 12.
Further Configuration
In a real application, we will most likely want to override many of the default configurations for our application. For example, we will want to implement custom error handling and security.
Here we override the responseStatusExceptionHandler to set the status code to 418 (I’m a teapot6) which is an actual HTTP status code that exists (just for demonstration purposes). There are many methods that you can override to provide your own custom logic.
- 1.
This annotation tells Spring Security to secure your WebFlux application.
- 2.
We define what paths are allowed to all users using the ant-pattern where “**” means any directory or directories. This allows everyone access to the main page and static files.
- 3.
Here we make sure that a user must be logged in to reach any path under the “/user/” path.
- 4.
This line converts all Users from the UserRepository into a List. This is then passed to the MapReactiveUserDetailsService which provides users to Spring Security.
- 5.
You must define a PasswordEncoder. Here we define a plaintext encoding just for demo purposes. In a real system, you should use a StandardPasswordEncoder or BCryptPasswordEncoder.
Testing
This simple test boots up our Spring Boot application and verifies that the root page returns with HTTP OK (200) status code and the body contains the text “Welcome to”. Using “webEnvironment = WebEnvironment.RANDOM_PORT” specifies that the Spring Boot application should pick a random port to run locally on every time the test is run.
Spring Data R2DBC
R2DBC7 (Reactive Relational Database Connectivity ) is a standard and programming interface for integrating with a relational database such as MySQL in a reactive, nonblocking way.
Although still at an early stage, there are several implementations of drivers including MySQL, H2, Microsoft SQL Server, and PostgreSQL.
R2DBC is covered more fully in Chapter 6.
Netty or Tomcat
By default, Spring WebFlux will use an embedded Netty web container. However, if spring-web is included anywhere on your classpath, your application will run using Tomcat instead. Spring Web pulls in Tomcat as a dependency, and that is the default from auto-configuration.
WebFlux supports running either using Netty (an asynchronous, nonblocking, event-driven network application framework) or the Servlet 3.1 nonblocking standard (using Tomcat or Jetty).
To ensure you run on Netty, you should exclude Tomcat from your dependencies.
WebClient
If you have Spring WebFlux on your classpath, you can also use WebClient to call remote web services. Compared to RestTemplate, this client has a more functional feel and is fully reactive, using Netty as the concurrency engine.
This builds a WebClient with a given baseUrl so that all requests made will start with this URL. It also provides a cookie and header to use for every request. There are many more methods to configure the WebClient.8