Going Reactive With Spring Webflux

Rashmi Shehana
6 min readNov 1, 2019

--

— Developing a reactive REST application from scratch —

Spring Webflux framework is a part of Spring 5. It provides reactive programming support for web applications in Java. In this article, we are going to create a reactive REST application using Spring Webflux.

What is Reactive Programming?

“Reactive programming is a programming paradigm oriented around data flows and the propagation of change.” It is about writing code that defines how to react to changes such as user inputs, data coming from a stream, a change in the state of a system, etc in a non-blocking manner.

We often use asynchronous processing to make the system fast and responsive when there is a situation to interact with huge volumes of data or multi-user environments. But in Java asynchronous system can become really troublesome and make the code hard to read and maintain. We can use Reactive programming in such situations because it simplifies dealing with asynchronous flows.

Spring Webflux Project Setup

In this example, we are using MongoDB to store our data. Spring provides an artifact ‘reactive MongoDB’ which includes reactive DB driver to perform DB operations asynchronously.

Now let’s start developing our reactive REST application using Spring Webflux.

Step 01 :- Go to the website https://start.spring.io and create a sample project. Select following dependencies for the project.

  1. Spring Reactive Web
  2. Spring Data Reactive MongoDB
Image 1.1 : Generating a new sample project in https://start.spring.io
Image 1.2: Generating a new sample project in https://start.spring.io

Now your pom.xml should be included above mentioned dependencies.

Step 02:- Create a project structure same as in the following image.

Image 2.1: Project File Structure

Step 03:- Add following code in resources/application.properties file. This is used to create our MongoDB database with name ‘studentDB’ in localhost.

spring.data.mongodb.uri=mongodb://localhost:27017/studentDB

Step 04:- Implement ‘student’ model.

student.java

package com.example.demo.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;


@Document
public class Student {

@Id
private int id;

private String name;
private int age;
private String university;
private double gpa;

public Student (){
}

public Student (String name, int age,String university, double gpa){
this.name = name;
this.age = age;
this.university=university;
this.gpa=gpa;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getUniversity() {
return university;
}

public void setUniversity(String university) {
this.university = university;
}

public double getGpa() {
return gpa;
}

public void setGpa(double gpa) {
this.gpa = gpa;
}
}

Annotations.

@Document:-marks a class as being a domain object that we want to persist to the database.

@Id:- marks a field in a model class as the primary key.

Step 05:- Implement Student_Repository

You can extend the class ‘ReactiveMongoRepository’ in Student_Repository and use pre-defined methods in ‘ReactiveMongoRepository’. ‘ReactiveMongoRepository’ is a Mongo specific interface with reactive support. When extending ReactiveMongoRepository, I have given parameters as <Student, Integer>. That means that I expect to apply this repository on model ‘Student’ and the type of the ‘id’ in ‘Student’ model is an integer.

Student_Repository.java

package com.example.demo.repository;

import com.example.demo.model.Student;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface Student_Repository extends ReactiveMongoRepository<Student,Integer> {

}

Annotations

@Repository:- This marks that the class provides the mechanism for storage, retrieval, search, update and delete operation on objects.

Step 06:- Implement StudentsController

First, let's create a controller method to create a student in DB.

@PostMapping
public Mono<ResponseEntity<Student>> create(@RequestBody Student student) {
return student_repository.save(student)
.map(savedStudent -> ResponseEntity.ok(savedStudent))
.defaultIfEmpty(ResponseEntity.notFound().build());
}

In here, student_repository.save(student) method is a pre-defined method used to save the created student in DB.

I have used the created user as a Request body parameter in this method. Because of that when you call this method, you have to send a student object with relevant parameters in the request body as follows.


{
"id": 1,
"name": "Rashmi Shehana",
"age": 22,
"university": "UCSC",
"gpa": 3.69

}

Now, create the controller methods to get one student and get all the students from DB.

@GetMapping
public Flux<Student> getStudents(){
return student_repository.findAll();
}

@GetMapping("/{studentId}")
public Mono<ResponseEntity<Student>> getStudentById(@PathVariable int studentId){
return student_repository.findById(studentId)
.map(student -> ResponseEntity.ok(student))
.defaultIfEmpty(ResponseEntity.notFound().build());
}

FindAll() and findById(id) are the pre-defined methods with reactive supports.

Next, we will create a method to update an existing student in DB.

@PutMapping("/{studentId}")
public Mono updateStudent(@PathVariable int studentId, @RequestBody Student student){
return student_repository.findById(studentId)
.flatMap(selectedStudentFromDB ->{
selectedStudentFromDB.setName(student.getName());
selectedStudentFromDB.setAge(student.getAge());
selectedStudentFromDB.setUniversity(student.getUniversity());
selectedStudentFromDB.setGpa(student.getGpa());

return student_repository.save(selectedStudentFromDB);
})
.map(updatedStudent -> ResponseEntity.ok(updatedStudent))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

We have to send an updated student object in request body same as in create method. In updateStudent method, first, we get access to the particular student with ‘studentId’ using findById method. Then update the student’s details in database using the details in object we send in request body and again save it in database with save method.

Finally, we will create a method to delete student.

@DeleteMapping("/student/{id}")
public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable(value = "id") int studentId) {

return student_repository.findById(studentId)
.flatMap(selectedStudentFromDB ->
student_repository.delete(selectedStudentFromDB)
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
)
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

What we have done in this method is finding a student using studentId and delete it from the database.

The final Student_Controller.java will be like this altogether.

package com.example.demo.controller;

import com.example.demo.model.Student;
import com.example.demo.repository.Student_Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/students")

public class StudentsController {

@Autowired
private Student_Repository student_repository;


@PostMapping
public Mono<ResponseEntity<Student>> create(@RequestBody Student student) {
return student_repository.save(student)
.map(savedStudent -> ResponseEntity.ok(savedStudent))
.defaultIfEmpty(ResponseEntity.notFound().build());
}

@GetMapping
public Flux<Student> getStudents(){
return student_repository.findAll();
}

@GetMapping("/{studentId}")
public Mono<ResponseEntity<Student>> getStudentById(@PathVariable int studentId){
return student_repository.findById(studentId)
.map(student -> ResponseEntity.ok(student))
.defaultIfEmpty(ResponseEntity.notFound().build());
}

@PutMapping("/{studentId}")
public Mono updateStudent(@PathVariable int studentId, @RequestBody Student student){
return student_repository.findById(studentId)
.flatMap(selectedStudentFromDB ->{
selectedStudentFromDB.setName(student.getName());
selectedStudentFromDB.setAge(student.getAge());
selectedStudentFromDB.setUniversity(student.getUniversity());
selectedStudentFromDB.setGpa(student.getGpa());

return student_repository.save(selectedStudentFromDB);
})
.map(updatedStudent -> ResponseEntity.ok(updatedStudent))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

@DeleteMapping("/student/{id}")
public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable(value = "id") int studentId) {

return student_repository.findById(studentId)
.flatMap(selectedStudentFromDB ->
student_repository.delete(selectedStudentFromDB)
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
)
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}

Step 07:- Now we have done with our REST application. You can start tomcat server and check how it works. I will run this on postman.

Image 7.1:-Creating a student
Image 7.2-: Getting all the students
Image 7.3:- Getting only one student by id
Image 7.4:- Updating a student
Image 7.5:- Deleting a student

Conclusion

Spring Webflux is an effective solution for the problems which occur due to using an old object-oriented language like java to implement asynchronous processes.

You can get the source code in Github through the following link.

Contact me on Fiverr.
https://www.fiverr.com/share/YQj7DR

--

--

Rashmi Shehana

Computer Science Undergraduate, University of Colombo School of Computing