Your web browser is out of date. Update your browser for more security, speed and the best experience on this site.

Update your browser
CapTech Home Page

Blog July 25, 2018

Annotation Driven Reactive Web API(s) with Spring WebFlux

Spring Framework 5 provides first class support for Reactive Web API development using Spring WebFlux.

Spring WebFlux

contains support for Reactive HTTP Rest API(s), WebSocket applications, and Server-Sent Events.

Spring WebFlux is responsive, resilient, scalable, and message-driven. Spring WebFlux uses

Project Reactor as its core, and it represents a complete reactive

reimplementation of Spring Web MVC.

Unlike Spring Web MVC, Spring WebFlux gives developers the ability to code API(s) that run in an event-loop

style with a small number of threads, thus allowing these API(s) to handle more requests than the traditional

thread-per request based model of Spring MVC. Spring WebFlux also provides a new reactive

WebClient

that streams data to the client when calling downstream API(s).

Spring Web MVC and Spring WebFlux share many of the same design patterns, but their implementation differs because Spring WebFlux runs on a reactive layer that is fully non-blocking. Spring WebFlux is designed to run on Servlet 3.1+, and can run on non-servlet runtimes such as Netty, Jetty, Tomcat, and Undertow. The default runtime is Netty.

For Web API(s), Spring WebFlux supports two programming models, one based off annotations, and one that is fully functional. The annotation-based model uses the exact same annotations and semantics as Spring Web MVC. It uses annotations such as @Controller, @Service, and @Repository, the difference being that the underlying MVC layer is now reactive and support types like Flux and Mono. For more information on Flux and Mono please read my previous blog on Project Reactor.

Spring 5 has also retrofitted Spring Security to be fully reactive, and has implemented reactive programming for Spring Data Mongodb, Cassandra, Redis, and Couchbase. All the aforementioned databases have Java drivers that support non-blocking access to them. Unfortunately JDBC drivers lack this support, therefore there is no reactive Spring Data JPA or JDBC.

The code below is an example of a Spring WebFlux Reactive Web API using annotated controllers. The full code for this sample Web API is on Github. On a subsequent blog I will depict the same Web API using the reactive functional style of programming that Spring WebFlux supports. This Web API is a simple movie API that uses an embedded Mongodb database as its data store. The code below is coded using Java 9 and also includes an example endpoint for using Server Sent Events with Spring WebFlux.

Maven pom.xml for Spring WebFlux

 org.springframework.bootspring-boot-starter-data-Mongodb-reactiveorg.springframework.bootspring-boot-starter-webfluxde.flapdoodle.embedde.flapdoodle.embed.mongo

In the code snippet above the Maven dependencies for Spring Boot Mongodb Reactive, Spring Boot Webflux and Flapdoodle are included. Maven is a dependency management tool for Java applications. Flapdoodle is an embed Mongodb compliant database that can run as soon as a Spring Boot application is started. Flapdoodle is usually used for integration and unit testing when an actual Mongodb database is not available.

Spring Data Mongodb Model POJO

@Document
public class Movie {

 @Id
 private String id;
 private String title;
 private String rating;
 private String description;

 // Getters and Setters
 ...
}

In the code snippet above a Java POJO (Plain Old Java Object) is annotated with the @Document annotation and its id attribute is annotated with the @Id annotation. These annotations allow for the movie object to be stored as a Document within a movie collection in Mongodb. These annotations provide the same functionality that Hibernate provides for Java applications that use relational databases. Please check the following Spring DataMongodb documentation for more information on annotations that can be used with Java POJOs.

Spring Data Mongodb Reactive Repository

@Repository
public interface MovieRepository extends ReactiveMongoRepository {

 Flux findByRating(String rating);
}

In the code snippet above a Java interface is annotated with the @Repository annotation which enhances this interface with methods that allow for the creation, reading, updating, and deletion of data saved to Mongodb. In this case it is the movies saved to the Mongodb movie collection. The method declaration in the interface, uses Spring Data's Method Query DSL technology to allow for the creation of data access code based off the method name. In the case above, just by Spring Data seeing the method signature, it creates code that allows you to find movies by rating. The search parameter is passed via the method parameters. This method also returns a stream of movies using the Flux data structure from Project Reactor. Spring Data Repositories are a very powerful and quick method to create data access objects for a Spring Boot application.

The Movie Service implementation

@Service
public class MovieServiceImpl implements MovieService {

 @Autowired
 private MovieRepository movieRepository;

 @Override
 public Flux list(){
 return movieRepository.findAll();
 }

 @Override
 public Flux findByRating(final String rating){
 return movieRepository.findByRating(rating);
 }

 @Override
 public Mono update(String id, MovieRequest movieRequest) {

 return movieRepository.findById(id).flatMap(existingMovie -> {

 if(movieRequest.getDescription() != null){
 existingMovie.setDescription(movieRequest.getDescription());
 }
 if(movieRequest.getRating() != null){
 existingMovie.setRating(movieRequest.getRating());
 }
 if(movieRequest.getTitle() != null) {
 existingMovie.setTitle(movieRequest.getTitle());
 }

 return movieRepository.save(existingMovie);

 });
 }

 @Override
 public Mono create(Mono movieRequest) {

 return movieRequest.flatMap(newMovie -> {

 Movie movie = new Movie();

 if(newMovie.getDescription() != null){
 movie.setDescription(newMovie.getDescription());
 }
 if(newMovie.getRating() != null){
 movie.setRating(newMovie.getRating());
 }
 if(newMovie.getTitle() != null) {
 movie.setTitle(newMovie.getTitle());
 }

 return movieRepository.save(movie);

 });
 }

 @Override
 public Mono read(String id) {
 return movieRepository.findById(id);
 }

 @Override
 public Mono delete(String id) {
 return movieRepository.findById(id)
 .flatMap(oldValue -> movieRepository.deleteById(id).then(Mono.just(oldValue)));
 }
}

The code snippet above depicts the Service layer of a Spring Boot application. This class implements methods to read, delete, list, update, and create data. The list method returns a Flux of movies directly from the Repository layer of the Web API. The findByRating method also returns a Flux of movies filtered by their ratings. The read method returns a Mono with one movie that is found by an id. A Mono represents one item that can be returned asynchronously, unlike a Flux which returns a stream of items. The create and update methods make use of flatMap since the mongoRepository.save() method returns a Mono, thus making the methods result a Mono>, using flatMap flattens the result out to simple a Mono. The delete method fetches a movie to be deleted from the Repository layer, once this is complete, it returns the movie deleted. Note that once the findById is completed, the deleteById method is triggered. Once the deleteById method completes, we are still within the flatMap context and the value from the findById method is still available, this value is re-emitted as the return value using the Mono.just() method. Functional Programming with Mono and Flux can be complex, the following tutorial provides a hands on and interactive way to better learn the functionalities that Mono and Flux provide.

Movie Rest Controller

@RestController
public class MovieRestController {

 @Autowired
 private MovieService movieService;

 @GetMapping(value = "/movies")
 public Flux list() {

 return movieService.list();
 }

 @GetMapping(value = "/moviesByRating")
 public Flux> findByRating(
 @RequestParam(value = "rating", required = false) final String rating) {

 return movieService.findByRating(rating)
 .map(m -> new ResponseEntity<>(m, HttpStatus.OK));

 }

 @GetMapping("/movies/{movieId}")
 public Mono> read(
 @PathVariable("movieId") final String movieId) {

 return movieService.read(movieId)
 .map(m -> new ResponseEntity<>(m, HttpStatus.OK))
 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
 }

 @DeleteMapping("/movies/{movieId}")
 public Mono> delete(
 @PathVariable("movieId") final String movieId) {

 return movieService.delete(movieId)
 .map(m -> new ResponseEntity<>(m, HttpStatus.OK))
 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

@PutMapping("/movies/{movieId}")
public Mono> update(
 @PathVariable("movieId") final String movieId,
 @RequestBody final MovieRequest movieRequest) {

 return movieService.update(movieId, movieRequest)
 .map(m -> new ResponseEntity<>(m, HttpStatus.OK))
 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));

}

@PostMapping("/movies")
public Mono> create(
 @RequestBody final Mono movieRequest) {

 return movieService.create(movieRequest)
 .map(m -> new ResponseEntity<>(m, HttpStatus.OK))
 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));

}

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux getMovieEvents() {
 return Flux.interval(Duration.ofSeconds(1))
 .map(val ->
 new Movie("movie1", "1", "movie1")
 );
}

}

The code snippet above represents a Spring Rest Controller. The Spring Rest Controller is usually the entry point to a Spring based Web API. The methods in this class are annotated with annotations that allow for the implementation of methods that allow for HTTP operations such as GET, POST, PUT, DELETE on the Web API. Much of the code here is exactly the same as would be in a typical Spring Web MVC API, the only difference here is that Mono and Flux are returned by the Rest Controller methods. This is a perfect example of how Spring has been able to seamlessly integrate Spring WebFlux in the existing code patterns used in Spring Web MVC. The getMovieEvents method uses Server Sent Events to send a stream of movies to the client every second, this method demonstrates how easy it is to send real time data back to a client using Spring Webflux.

Driver application code

@SpringBootApplication
public class Application {

 public static void main(String[] args) {
 SpringApplication.run(Application.class, args);
 }

 @Bean
 public CommandLineRunner initDatabase(MovieRepository repository) {
 return args -> {
 Flux.just(
 new Movie("movie1", "1", "movie1"),
 new Movie("movie2", "1", "movie2"),
 new Movie("movie3", "5", "movie3"),
 new Movie("movie4", "1", "movie4"),
 new Movie("movie5", "3", "movie5"),
 new Movie("movie6", "1", "movie6"),
 new Movie("movie7", "2", "movie7"),
 new Movie("movie8", "3", "movie8"),
 new Movie("movie9", "1", "movie9"),
 new Movie("movie10", "2", "movie10"),
 new Movie("movie11", "1", "movie11"),
 new Movie("movie12", "3", "movie12"),
 new Movie("movie13", "1", "movie13"),
 new Movie("movie14", "4", "movie14"),
 new Movie("movie15", "1", "movie15"),
 new Movie("movie16", "4", "movie16")
 ).flatMap(repository::save)
 .thenMany(repository.findAll())
 .subscribe(System.out::println);

 };
 }
}

In the code snippet above, data is inserted into the Mongodb movie collection using the Flux data structure from Project Reactor. Once the data is inserted, a find all query is executed, and the data is subsequently printed out to the console. Since this is being run outside of the Spring Web Context, we must subscribe to the Reactive Publisher manually in order for the code to execute. This code populates the Mongodb database before the Spring Boot Web API is deployed.

Uses cases for Spring WebFlux

Spring WebFlux allows your applications to handle more with less. If you have a database or downstream services that do not return an immediate response, then that latency isn't propagated throughout the architecture. The application does not block, waiting for a downstream response, instead it continues to service requests allowing the service to have greater capacity. Spring WebFlux is not a silver bullet, and it is not meant to be used in all situations, it is a technology that truly depends on your use case. If your service is implemented using Spring MVC and it meets your quality of service standards then there is no need to recode the service to be reactive. But if your service is suffering from poor throughput and scalability then you have the option to go reactive or scale horizontally.

Businesses usually have two options when they are faced with increased traffic to their applications, whether to scale vertically or horizontally to meet demand. Vertical scaling includes deploying APIs on more powerful virtual machines with more CPU and Memory, and horizontal scaling includes creating more replicas of an API using a technology like Docker with Kubernetes. Spring WebFlux and Project Reactor allows your APIs to use existing resources much more efficiently allowing you to make full use of available resources and only scale when you truly have to. This can certainly introduce cost savings in the maintenance and operation of Web APIs.

Spring 5 will support traditional Spring MVC and Spring WebFlux side by side. Spring MVC applications will continue to be able to run on Servlet 3.1+, and Spring WebFlux will have the option of running on Servlet 3.1+, Tomcat, Jetty, Undertow, and Netty. For more information on the Spring Framework and Spring WebFlux, please check out the links below.