In this post, I will demonstrate a sample web application which makes use of Spring Web-Flux. As detailed here, the Web-Flux support in Spring 5+ supports two different programming style - the traditional annotation based style and the new functional style. In this post I will be sticking to the traditional annotation style and will follow it up in another blog post(now available here) detailing a similar application but with endpoints defined in a functional style. My focus is going to be purely 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, another maintaining a duplicated data which makes searching for 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 which return one instance of an entity now return a Mono type and operations which return more than one element return a Flux type.
Given this let me touch on one quick use of returning the reactive types, when a Hotel is updated I have to delete the duplicated data maintained via HotelByLetter repository and recreate it again, this can be accomplished something like the following, using the excellent operators provided by 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 POJO's. 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 also 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, @PostMapping is unchanged, what is different is the return types - for instances where atmost 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 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.
Great article
ReplyDeleteMongoDB also has reactive java driver.
https://mongodb.github.io/mongo-java-driver-reactivestreams/
Hi,
ReplyDeleteShouldn't you be returning Mono instead of Mono ?