Spring WebFlux: First Steps
Spring 5 comes with WebFlux — support for reactive programming practices in Spring applications. See how to introduce it into an old annotation-style controller.
Join the DZone community and get the full member experience.
Join For FreeSpring WebFlux is used for denoting the Reactive programming support in Spring's web layer. It provides support for both creating reactive, server-based web applications and also has client libraries to make remote REST calls.
In this post, I will demonstrate a sample web application that makes use of Spring WebFlux. As detailed here, the WebFlux support in Spring 5+ supports two different programming styles — the traditional, annotation-based style and the new functional style. In this post, I will be sticking to the traditional annotation style and try to follow it up in another blog post detailing a similar application, but with endpoints defined in a functional style. My focus is going to be the programming model.
Data and Services Layer
I have a fairly simple REST interface supporting CRUD operations of a Hotel resource with a structure along these lines:
public class Hotel {
private UUID id;
private String name;
private String address;
private String state;
private String zip;
....
}
I am using Cassandra as a store of this entity, and using the reactive support in Spring Data Cassandra allows the data layer to be reactive, supporting an API that looks like this. I have two repositories here, one facilitating the storage of the Hotel entity above, and another maintaining duplicated data, which makes searching for a Hotel entity by its first letter a little more efficient:
public interface HotelRepository {
Mono<Hotel> save(Hotel hotel);
Mono<Hotel> update(Hotel hotel);
Mono<Hotel> findOne(UUID hotelId);
Mono<Boolean> delete(UUID hotelId);
Flux<Hotel> findByState(String state);
}
public interface HotelByLetterRepository {
Flux<HotelByLetter> findByFirstLetter(String letter);
Mono<HotelByLetter> save(HotelByLetter hotelByLetter);
Mono<Boolean> delete(HotelByLetterKey hotelByLetterKey);
}
The operations that return one instance of an entity now return a Mono type, and operations that return more than one element return a Flux type.
Given this, let me touch on one quick use of returning reactive types: When a Hotel is updated, I have to delete the duplicated data maintained via the HotelByLetter repository and recreate it again. This can be accomplished with something like the following, using the excellent operators provided by the Flux and Mono types:
public Mono<Hotel> update(Hotel hotel) {
return this.hotelRepository.findOne(hotel.getId())
.flatMap(existingHotel ->
this.hotelByLetterRepository.delete(new HotelByLetter(existingHotel).getHotelByLetterKey())
.then(this.hotelByLetterRepository.save(new HotelByLetter(hotel)))
.then(this.hotelRepository.update(hotel))).next();
}
Web Layer
Now to the focus of the article: support for annotation-based reactive programming model support in the web layer!
The @Controller and @RestController annotations have been the workhorses of the Spring MVC's REST endpoint support for years now. Traditionally, they have enabled taking in and returning Java POJOs. These controllers in the reactive model have now been tweaked to take in and return the Reactive types — Mono and Flux in my examples — but additionally the Rx-Java 1/2 and Reactive Streams types.
Given this, my controller, in almost its entirety, looks like this:
@RestController
@RequestMapping("/hotels")
public class HotelController {
....
@GetMapping(path = "/{id}")
public Mono<Hotel> get(@PathVariable("id") UUID uuid) {
return this.hotelService.findOne(uuid);
}
@PostMapping
public Mono<ResponseEntity<Hotel>> save(@RequestBody Hotel hotel) {
return this.hotelService.save(hotel)
.map(savedHotel -> new ResponseEntity<>(savedHotel, HttpStatus.CREATED));
}
@PutMapping
public Mono<ResponseEntity<Hotel>> update(@RequestBody Hotel hotel) {
return this.hotelService.update(hotel)
.map(savedHotel -> new ResponseEntity<>(savedHotel, HttpStatus.CREATED))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@DeleteMapping(path = "/{id}")
public Mono<ResponseEntity<String>> delete(
@PathVariable("id") UUID uuid) {
return this.hotelService.delete(uuid).map((Boolean status) ->
new ResponseEntity<>("Deleted", HttpStatus.ACCEPTED));
}
@GetMapping(path = "/startingwith/{letter}")
public Flux<HotelByLetter> findHotelsWithLetter(
@PathVariable("letter") String letter) {
return this.hotelService.findHotelsStartingWith(letter);
}
@GetMapping(path = "/fromstate/{state}")
public Flux<Hotel> findHotelsInState(
@PathVariable("state") String state) {
return this.hotelService.findHotelsInState(state);
}
}
The traditional @RequestMapping, @GetMapping, and @PostMapping is unchanged. What is different is the return types. For instance, where at most 1 result is expected, I am now returning a Mono type, and where a list would have been returned before, now a Flux type is returned.
With the use of the reactive support in Spring Data Cassandra, the entire web-to-services-and-back setup is reactive and, specifically for the focus of the article, eminently readable and intuitive.
It may be easier to simply try out the code behind this post, which I have available in my GitHub repo here.
Published at DZone with permission of Biju Kunjummen, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments