[Tuto] Building a Reactive RESTful API with Spring WebFlux Java
The year 2014 has seen the birth of the Reactive Manifesto which emphasizes the development of responsive, resilient, elastic and message driven systems. Since then, more than 22.000 people signed it and some major librairies were born to help developers build such reactive systems.
In the Java world first came RxJava and then Reactor, developed by Pivotal, the company behind Spring Framework. This is how Spring Framework 5.0.0 brought a whole new Reactive Stack beside the famous Servlet Stack, in Sept. 2017, named WebFlux.
“Spring WebFlux is a non-blocking web framework built from the ground up to take advantage of multi-core, next-generation processors and handle massive numbers of concurrent connections.” (spring.io)
As more and more clients demand them, it becomes important to be able to build reactive systems. And, let’s be honest, it’s a very exciting to build blazing fast systems and discover new technologies 😎. Moreover, reactive systems are great Cloud Native apps. This is exactly why the goal of this post is to guide you through building your first (maybe not) reactive RESTful API with Spring WebFlux Java.
Ready ?
What you will build
In this tutorial, you will build a RESTful CRUD API to manage users represented as the Person
class. Your API will be able to list all the users it knows, retrieve a specific one, create, add, update and delete ones.
We will use a very simple model to represent a Person. It will have a first name, a family name, an age and an id.
A swagger for this API is available here, and the code of the resulting server on GitHub.
You will build your API following these 3 steps:
- Modeling the application domain
- Creating a first route
/persons
and instantiating a server that runs - Creating the other routes
Spring WebFlux makes use of Reactor which provides the Mono and Flux classes. If you have never worked with them, I recommend you read the articles I wrote to start with Reactor from the ground up. The first one is [Reactor Java#1] How to create Mono and Flux ?
Project set up
We will use Java 8 and gradle as the dependency management and build tool. If not familiar with gradle, all you have to know is that the configuration of the build and the dependencies goes into the build.gradle
file. Don’t worry, I’ll tell you each time you need to update this file.
Considering that you will add your Main
class in the package com.yourname.reactivewebserver
, your build.gradle
should be the following.
group 'com.yourname'
version '0.0.1'apply plugin: 'java'
apply plugin: 'application'mainClassName = group + '.reactivewebserver.Main'
sourceCompatibility = 1.8repositories {
mavenCentral()
}dependencies {
// compile group: 'groupId', name: 'artifactId', version: 'v'
}
To test this set up you can create a Main
class in the package com.yourname.reactivewebserver
with a main function printing the famous Hello World!
in the console. To run it, type ./gradlew run
in a terminal.
Conventions
As functional programming fits well with asynchronous and concurrent programming we will write our code in a functional way. This is why I’ll use the Java 8 Optional and Stream interfaces. Also, data structures will be immutable.
It’s totally ok if you’re not familiar with this paradigm for this tutorial. If curious about discovering the basis of functional programming, you could start from here.
Modeling the application domain
The Person
class described above is pretty straightforward. Add it into a domain
package, so in com.yourname.reactivewebserver.domain
.
public class Person {
private final String id;
private final String firstName;
private final String familyName;
private final int age;
public Person(String id, String firstName, String familyName, int age) {
this.id = id;
this.firstName = firstName;
this.familyName = familyName;
this.age = age;
}
public String getId() { return id; }
public String getFirstName() { return firstName; }
public String getFamilyName() { return familyName; }
public int getAge() { return age; }
}
As this class doesn’t have setters, Jackson, the most used JSON
library in Java, won’t be able to create instances of your class. This is why we will have to write our own JSON
parser for the Person
class.
Json Reader and Writer
First, add a dependency to Jackson. To do it, open your build.gradle
file and replace the empty dependencies {}
block with:
dependencies {
compile group: 'io.projectreactor.ipc', name: 'reactor-netty', version: '0.7.6.RELEASE'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
}
Then, create a json
package in com.yourname.reactivewebserver
and add the following two classes into it. They will be used later as reader and writer.
Creating a first route
The first route you will expose will map to the operation of retrieving the list of persons the app holds. It will be associated to GET /persons
and return a JSON
document.
To create this first route, we already have our JSON writer. Now we need a service to retrieve the list of persons that we will name PersonService
, another to deal with the HTTP layer, here PersonApi
, and a Main
to start our application.
Spring dependencies
Go back to the build.gradle
file and add the following dependencies:
compile group: 'org.springframework', name: 'spring-webflux', version: '5.0.5.RELEASE'
compile group: 'org.springframework', name: 'spring-context', version: '5.0.5.RELEASE'
The main function
Let’s begin with the main function. Following the official documentation, our function is:
public static void main(String[] args) {
final PersonApi personApi = null; // TODO
final RouterFunction<ServerResponse> apiRoot =
RouterFunctions.nest(
RequestPredicates.path("/persons"),
personApi.routerFunction
);
final HttpHandler httpHandler = RouterFunctions.
toHttpHandler(apiRoot);
final ReactorHttpHandlerAdapter adapter =
new ReactorHttpHandlerAdapter(httpHandler);
HttpServer.create(8080).startAndAwait(adapter);
}
The above code instantiates the elements required by Spring Webflux. Do not focus on the PersonApi
instance at the moment, we will create it in a few minutes. Instead, let’s focus on the RouterFunction
element. RouterFunctions
are elements that the Spring router will use to route requests to the proper function and return a 404 if the request doesn’t match any predicate.
In our case, if the user sends a request which path starts with /persons
, Spring will pass the request to the personApi RouterFunction. Otherwise it will respond with a 404
status code.
Don’t worry if you don’t understand this part yet. We will write more RouterFunctions
in the PersonApi
component soon.
The PersonApi
The PersonApi
is responsible for the HTTP layer of the server. On each request that matches a route that we will create, it will be responsible for verifying the correctness of the request, then executing the relevant operation on the data, handling the possible errors and then creating the proper response.
Let’s do exactly that for the GET /persons
that will respond with the full list of persons the app holds.
package com.yourname.reactivewebserver.rest;public class PersonApi {
public final RouterFunction<ServerResponse> routerFunction;
private final PersonService personService; public PersonApi(PersonService personService) {
this.personService = personService;
this.routerFunction = RouterFunctions.
route(RequestPredicates.GET(""), this::getAllPersons);
}private Mono<ServerResponse> getAllPersons(ServerRequest request){
return this.personService.list() // returns a Flux<Person>
.collectList()
// turns the flux into a Mono<List<T>> to allow sending a single response
.flatMap(JsonWriter::write)
.flatMap((json) -> ServerResponse.ok()
.body(Mono.just(json), String.class)
).onErrorResume(
JsonProcessingException.class,
(e) -> ServerResponse.status(INTERNAL_SERVER_ERROR)
.body(Mono.just(e.getMessage()), String.class)
);
}
}
We won’t instantiate it in the main
right away. First we will create the PersonService
class.
The PersonService
In this tutorial, our PersonService
will not persist the data into a database. Instead, it will store the data in-memory.
Now, let’s first create the list()
method that we used in PersonApi
. It will return aFlux<Person>
containing all the Person
instances the app holds.
The code for our PersonService
is the following:
package com.yourname.reactivewebserver.services;public class PersonService {
private final List<Person> persons; public PersonService() {
this.persons = Stream.of(
// Create new instance of Person here, as many as you wish
new Person("id", "foo", "bar", 1)
).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
} public Flux<Person> list() {
return Flux.fromIterable(this.persons);
}
}
Time to test it !!
Yes, as the title suggest, it is time to start your server for the first time.
Go back to the main
function and replace final PersonApi personApi = null; // TODO
with final PersonApi personApi = new PersonApi(new PersonService());
Now you can ./gradlew run
in your terminal :). Open Postman, or Insomnia and make a GET request on http://localhost:8080/persons
. You should see the below response.
Is my server really reactive ?
Now it is time to make sure our server really is reactive, and, spoiler alert… NO it’s not, not yet.
To observe it, in the PersonApi.getAllPersons(…)
method, replace
.flatMap((json) ->
ServerResponse.ok().body(Mono.just(json), String.class))
with
.flatMap((json) -> {
System.out.println("Executing on thread: " +
Thread.currentThread().getName()
);
ServerResponse.ok().body(Mono.just(json), String.class)
})
Then, make a few requests on the server. You will observe that there are as many threads handling the requests as your computer’s CPU has. On my computer, it results running on 4 threads. Their name is reactor-http-nio-N
.
On one side, this is still 4 times better than single-threaded technologies. On the other side, we can make it much better.
A big improve in performances
In order to make the server use as many threads as it needs to, we will have to control the execution of the Mono
we use.
This can be done with Schedulers
, which are components provided by Reactor that are some highly configurable Thread Pools, coming with 4 pre-configured Schedulers: immediate
, single
, parallel
and elastic
.
For more information on Schedulers, please refer to the dedicated page of the official documentation here.
In our case, we will use the elastic
scheduler as it is the most efficient in most cases. You could prefer the parallel
scheduler if doing very intensive computing on the server. If this is the case, I suppose you already know what scheduling technique best fits your use case.
A word about the elastic
scheduler. As stated in the official documentation, “it creates new worker pools as needed, and reuse idle ones. Worker pools that stay idle for too long (default is 60s) are disposed. This is a good choice for I/O blocking work for instance”.
To configure the scheduler a Mono, or Flux, should use, call its subscribeOn(Scheduler s)
method. You can see this method as a configuration method. If calling it several times, the Mono
will subscribe on the first scheduler you (or the library’s author) passed it.
Now comes the question: where should we configure the scheduler to use ? The answer is: both in the service methods and the API methods. The one configured in the service method will allow fine tuning service execution, mostly dealing with I/O delays. This will be the scheduler used for the success path. And the one in the API will be set for the error path and request handler method that do not make use of the service.
Now, create a Config
class and add a public static final Scheduler SCHEDULER = Schedulers.elastic();
attribute into it. Then, add .subscribeOn(Config.SCHEDULER)
at the end of PersonService.list()
and PersonApi.getAllPersons(…)
.
There we are, our server became able to handle a lot more requests.
Creating the other routes
To help us handling errors I created a few Exception
classes that you can find here. I will use them often in the rest of this post. In the meantime, you can take a look at the utils
package I’ve done.
In this section, I will detail the most complicated case that is the creation of the POST /persons/{id}
route. This route will be mapped to an updateOnePersonById
method of the PersonService
. We’ll start with this method. Then your goal will be to build the other routes by yourself.
Person Service
public Mono<Boolean> updateOneById(Person person) {
return this.findById(person.getId()).
map((previous) ->
this.persons.remove(previous) && this.persons.add(person)
).
subscribeOn(Config.APPLICATION_SCHEDULER);
}
PersonApi method
The biggest piece comes in the PersonApi
class. Let’s first write an updateOnePersonById
method to handle the request. This method is :
private Mono<ServerResponse> updateOnePersonById(ServerRequest request) {
return request.bodyToMono(String.class).
flatMap(this::readPersonFromRequestBody).
filter((person) ->
person.getId().equals(
request.pathVariable(PERSON_ID_PATH_VARIABLE)
)
).
flatMap(this.personService::updateOneById).
flatMap((success) -> success ?
Responses.noContent() : Responses.internalServerError()
).
onErrorResume(
NotFoundResourceException.class,
Responses::notFound
).
onErrorResume(
InvalidRequestBodyException.class,
Responses::badRequest
).
switchIfEmpty(Responses.badRequest()).
subscribeOn(Config.APPLICATION_SCHEDULER);
}private Mono<Person> readPersonFromRequestBody(String body) {
return MonoUtils.fromOptional(
PersonReader.read(body),
() -> new InvalidRequestBodyException(Person.class)
);
}
The method does the following:
- Read the request body as a
String
, that actually is a JSON document. - Try to parse it and instantiate a
Person
instance. If impossible, the Mono becomes aMonoError<>
holding anInvalidRequestBodyException
. - Verify that the
id
in the path and theid
of thePerson
object sent in the request are consistent, because ids shouldn’t be changed. - Invoque the
PersonService.updateOneById
method to make the operation. - Handle the probably
false
value returned by the service to tell the user she made that an internal server error occurred. - In case a
NotFoundResourceException
was thrown before, returns a 404 Not Found response. - In case a
InvalidRequestBodyException
was thrown before, returns a 400 Bad Request response. - Returns a 400 Bad Request response in case the Mono is empty, which means that the id in the path and the one in the body are different.
- Apply the configured scheduler.
As you might have noticed, the goal in writing this code is to maximize the proportion of business logic as the Api is an interface. It is good practice to avoid writing implementation details code in your Api classes.
PersonApi routerFunction
Last, we have to add the route to the PersonApi
routerFunction. Here, we need the andRoute(predicate, handler)
method. This component becomes:
this.routerFunction = RouterFunctions.
route(GET(""), this::getAllPersons).
andRoute(
POST("/{id}").and(contentType(APPLICATION_JSON)),
this::getOnePersonById
);
There we are !!
Yes now we’re done with our second route. Restart the webserver and update the foo bar person with an HTTP request on /persons/{id}
😉.
Now, you can implement the other routes by yourself. You can always refer to my implementation on Github.
Congratulations !
Here we are, you built your first reactive webserver in Java with Spring WebFlux.
Thanks for reading. I hope this post helped you, and that you liked it. Don’t hesitate to write a comment below to help me improving it. Otherwise, any kind message would be greatly appreciated.
Have fun coding ! 🤘