CRUD Rest API With Spring Web-Flux Reactive Programming with Spring 5:-
Spring has released Reactive Web Framework and it is one of the hot topics in Spring world.Our controllers now run on Spring Reactive Engine. We will show a basic Spring Web-Flux Crud example.
Spring Framework 5 embraces Reactive Streams and Reactor for its own reactive use as well as in many of its core API’s.
Lets talk a bit about Reactive Programming and write CRUD Rest API With Spring Web-Flux Reactive Programming with Spring 5.
Spring Framework 5 includes a new spring-web-flux
module. The module contains support for reactive HTTP and Web Socket clients as well as for reactive server web applications including REST, HTML browser, and Web Socket style interactions.Spring Web-flux supports two different programming models:-
- Annotation-based with
@Controller
and the other annotations supported also with Spring MVC - Functional, Java 8 lambda style routing and handling
Below is the image that demonstrates the server side aspect of both the programming model.
So Now, let’s go ahead and implement a CRUD Rest Service which would do all operations related to User Object and understand Spring reactive module a bit further.
Download Code
Technologies used:-
- Spring 5.
- Spring Boot 2.
- Eclipse 4.4.
- JDK 1.8.
Project Dependencies:-
Spring Boot 2.0.
Spring Webflux.
Spring Reactive Data MongoDb.
Create Project :-
- Navigate to start.spring.io
- Enter Project Name and package name as required.
- Select
Reactive Web
andReactive MongoDb
as Project Dependencies. - Click on generate .
- Import Project in your favorite IDE and create following package structure as below.
Configure Your Application:-
We need to annotate our spring class with @EnableReactiveMongoRepositories
.Any annotation in reactive spring based modules will start with Enable..
followed by Reactive
.
We finally activate Spring Data Reactive MongoDB JPA repositories using the @EnableReactiveMongoRepositories
annotation which essentially carries the same attributes as the XML namespace does.
If no base package is configured it will use the one the configuration class resides in.
package com.frugalis.ReactiveRest;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@SpringBootApplication
@EnableReactiveMongoRepositories
public class ReactiveRestApplication {
@Autowired
UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(ReactiveRestApplication.class, args);
}
}
Let’s Configure MongoDB , we are going to use cloud-based MLAB .Please visit this link to understand setup MLAB .We are going to use mongo url in application.properties
spring.data.mongodb.uri=mongodb://<user>:<password>@ds163918.mlab.com:63918/spring
We are just going to use this as aproperty wee can also mention db specific properties separately .Please visit this link Spring Boot MongoDB+MLAB to check that and understand.
Database Access Using Reactive MongoDB:-
We are going to first create a user entity model and that is going to persist in our MongoDB using Spring reactive based rest api call.
package com.frugalis.ReactiveRest.entity;
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "Users")
public class Users implements Serializable {
@Id
int id;
String name;
String age;
public Users()
{
}
public Users(String name, String age) {
this.name = name;
this.age = age;
}
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 String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Users [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
The Users class is annotated with @Document
, indicating that it is a JPA entity. It is assumed that this entity will be mapped to a collection named Users
.The User’s id
property is annotated with @Id
so that JPA will recognize it as the object’s ID.Lets create a repository which extends some core Spring data interface to helps us not to write query and do the stuff for you.
We write our repository to access database using Spring data provided marker interface Repository.We are going to use @ReactiveMongoRepository
which extends @ReactiveCrudRepository
.These interfaces provides us with some basic operations needed such as save()
, update()
,findAll()
,findById()
etc.
we can also write templating methods which will internally generate query based on method signature.In above code we are writing a method findByName()
which will write a query to select the collection based on name.
package com.frugalis.ReactiveRest.repository;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import com.frugalis.ReactiveRest.entity.Users;
import reactor.core.publisher.Flux;
public interface UserRepository extends ReactiveMongoRepository<Users, Integer> {
Flux<Users> findByName(String name);
}
Understanding Flux and Mono in brief:-
We use Flux if we need to return a set of data stream which can emit 0..N elements:
Flux<String> flux = Flux.just("a", "b", "c");
Mono is a stream of 0..1 elements:
Mono mono = Mono.just("Hii");
As we are returning a single person hence we have used Mono .
Writing Controller :-
package com.frugalis.ReactiveRest.Controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.frugalis.ReactiveRest.entity.Users;
import com.frugalis.ReactiveRest.repository.UserRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/users")
public class UsersOldController {
@Autowired
UserRepository userRepository;
@GetMapping
public Flux<Users> getAllUser() {
return userRepository.findAll();
}
@PostMapping
public Mono<Users> createUser( @RequestBody Users user) {
return userRepository.save(user);
}
@GetMapping("/{id}")
public Mono<ResponseEntity<Users>> getUserById(@PathVariable(value = "id") int userId) {
return userRepository.findById(userId)
.map(savedTweet -> ResponseEntity.ok(savedTweet))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}
Using Router Functions:-
We use router functions to define our rest end points in functional way.We are going to understand the router functions in a separate article in detail.This is a basic simple use case of writing reactive router.
@Bean
RouterFunction<ServerResponse> helloRouterFunction() {
RouterFunction<ServerResponse> routerFunction =
RouterFunctions.route(RequestPredicates.path("/"),
serverRequest ->
ServerResponse.ok().body(Mono.just("Hello World!"), String.class));
return routerFunction;
}
Now, We want to load some data one our spring boot Application starts .Lets update our MainApplication.java
.
@Bean
CommandLineRunner runner() {
return args -> {
System.out.println("::::::::::::::::::::::");
Mono<Void> sss = userRepository.deleteAll();
sss.subscribe((e) -> {
}, Throwable::printStackTrace);
for (int i = 0; i <= 5; i++) {
Users user= new Users("Test"+i, "1" + i);
user.setId(i);
Mono<Users> data = userRepository.save(user);
System.out.println(data);
data.subscribe((e) -> {
System.out.println(e.toString());
}, Throwable::printStackTrace);
}
};
We can see after returning mono from userRepository
we have to subscribe , execution is going to happen only if you subscribe . As you know in my previous article Creating REST Service with Spring Boot we saw how to test the code using postman .Please follow that to test using Postman.
Integration Testing Rest API:-
Now that we have created a CRUD Rest API With Spring Web-Flux Reactive Programming with Spring 5 .It is time to write some integration test.We are going to use WebTestClient
.
It is a Non-blocking, reactive client for testing web servers. It uses the reactive WebClient
internally to perform requests and provides a fluent API to verify responses. Run this test class using Junit .
package com.frugalis.ReactiveRest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties.User;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import com.frugalis.ReactiveRest.entity.Users;
import com.frugalis.ReactiveRest.repository.UserRepository;
import reactor.core.publisher.Mono;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ReactiveRestApplicationTests {
@Autowired
private WebTestClient webTestClient;
@Autowired
private UserRepository userRepository;
@Test
public void saveUser() {
Users ps=new Users("dsld", "dfdf");
userRepository.deleteAll().subscribe();
webTestClient.post().uri("/users")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(ps), Users.class)
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString())
.expectBody()
.jsonPath("$.name").isEqualTo("dsld");
}
@Test
public void testUser() {
webTestClient.get().uri("/users")
.exchange()
.expectStatus().isOk()
.expectHeader().valueEquals("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString())
.expectBodyList(User.class).hasSize(1);
}
}