Spring Boot MongoDB Functional Reactive CRUD Example

Introduction

We will see here Spring Boot MongoDB Functional Reactive CRUD Example. We have seen our previous example on Spring Boot Functional Reactive Programming but we did not use any persistent storage like MongoDB but here we are going to use MongoDB for performing CRUD operations. We are going to use Spring 5’s Webflux API with reactive stream to implement our Spring Reactive program.

This is a Spring Reactive programming example implemented in a functional way using Java’s lambda expression.

If you want to know detail on Reactive Programming or Functional Reactive Programming then please read Spring Boot Functional Reactive Programming.

Prerequisites

Eclipse Neon, Java 1.8, Spring Boot 2.1.6, MongoDB 4, Gradle 5.4.1

Installing MongoDB in Windows

Now let’s move on to the implementation part of our Spring Boot MongoDB Functional Reactive CRUD Example, where CRUD means create, Read, Update and Delete operations.

Setting Up Project

Create a new gradle based project in Eclipse, the project structure may look to the similar image:

spring boot mongodb functional reactive crud example

Updating Build Script

Next we will update the default generated build.gradle script to include required dependencies.

In the below build script we have included spring boot webflux, spring boot mongodb reactive.

buildscript {
    ext {
        springBootVersion = '2.1.6.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
    mavenCentral()
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux:${springBootVersion}")
    implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive:${springBootVersion}")
}

Configuring MongoDB

As we mentioned in the prerequisites section, you need to go through Installing MongoDB in Windowsto install MongoDB and create database roytuts into it.

If we do not specify anything in the application.properties or application.yml file then Spring Boot, by default, connects to test database server at localhost:27017.

Here we will specify our roytuts database for connecting to it.

Notice also we have mentioned spring.jackson.default-property-inclusion=NON_NULL to ignore null field in JSON response.

Create below content into src/main/resources/application.properties file.

spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.database=roytuts
spring.jackson.default-property-inclusion=NON_NULL

Creating Entity Class

We will work with database, where we will perform CRUD operations. Therefore we need to create entity class to map out database column with Java class attributes.

We want to store forex details, such as, id, from currency, to currency and rate of the currency.

Another beauty of Spring Boot MongoDB reactive is the below class will create the required collection in the database. Though we have given a collection name. Even if we didn’t mention any column name, so it will create same column name in the table as the Java attributes.

So the corresponding forex entity class can be defined as:

package com.roytuts.spring.boot.reactive.mongodb.crud.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "forex")
public class Forex {
	@Id
	private String id;
	private String fromCur;
	private String toCur;
	private Double rateCur;
	public Forex() {
	}
	public Forex(String fromCur, String toCur, Double rateCur) {
		this.fromCur = fromCur;
		this.toCur = toCur;
		this.rateCur = rateCur;
	}
	public Forex(String id, String fromCur, String toCur, Double rateCur) {
		this.id = id;
		this.fromCur = fromCur;
		this.toCur = toCur;
		this.rateCur = rateCur;
	}
	//getters and setters
}

Creating VO Class

It is always good idea to create a VO or DTO class to return as a response object from router instead of returning the entity class as a response object.

The VO class is also similar to the above entity class.

package com.roytuts.spring.boot.reactive.mongodb.crud.vo;
public class ForexVo {
	private String id;
	private String fromCur;
	private String toCur;
	private Double rateCur;
	public ForexVo() {
	}
	public ForexVo(String id, String fromCur, String toCur, Double rateCur) {
		this.id = id;
		this.fromCur = fromCur;
		this.toCur = toCur;
		this.rateCur = rateCur;
	}
	//getters and setters
}

Creating Reactive Repository Interface

Spring MongoDB reactive provides built-in methods for performing basic CRUD operations in reactive programming and you don’t need to write any query method.

Another beauty of Spring MongoDB reactive is that you write Java method to query your database and Spring generates the corresponding query from your method for performing database operations.

package com.roytuts.spring.boot.reactive.mongodb.crud.repository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import com.roytuts.spring.boot.reactive.mongodb.crud.entity.Forex;
public interface ForexRepository extends ReactiveCrudRepository<Forex, String> {
}

We have just extended the ReactiveCrudRepository interface but we did not write any query method inside this repository because we will use Spring’s built-in methods for performing CRUD operations in reactive programming.

Creating Handler Functions

Handler function is one of the two important functions of Reactive Streams – Handler Function and Router Function.

Writing handler functions as lambdas’ is convenient, but perhaps lacks in readability and becomes less maintainable when dealing with multiple functions. Therefore, it is recommended to group related handler functions into a handler or controller class.

For example, here is a class that exposes a reactive forex repository.

In the below class, incoming HTTP requests are handled by a HandlerFunction, which is essentially a function that takes a ServerRequest and returns a Mono<ServerResponse>.

ServerRequest and ServerResponse are immutable interfaces and both are fully reactive by building on top of Reactor: the request expose the body as Flux or Mono; the response accepts any Reactive Streams Publisher as body.

You can find details here on Flux and Mono.

Notice you could have directly saved/updated entity objects into MongoDB or you could have fetched entity objects from MongoDB and send directly to the clients or end users but this is not a good idea. You will understand the problems or issues if you do so in a complex project with large number of collections or tables.

package com.roytuts.spring.boot.reactive.mongodb.crud.handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.roytuts.spring.boot.reactive.mongodb.crud.entity.Forex;
import com.roytuts.spring.boot.reactive.mongodb.crud.repository.ForexRepository;
import com.roytuts.spring.boot.reactive.mongodb.crud.vo.ForexVo;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
public class ForexHandler {
	@Autowired
	private ForexRepository repository;
	public Mono<ServerResponse> getForexList(ServerRequest request) {
		Flux<Forex> forexList = repository.findAll();
		Flux<ForexVo> forexVoList = forexList.map(f -> {
			return new ForexVo(f.getId(), f.getFromCur(), f.getToCur(), f.getRateCur());
		});
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(forexVoList, ForexVo.class);
	}
	public Mono<ServerResponse> getForexById(ServerRequest request) {
		String forexId = request.pathVariable("id");
		Mono<ServerResponse> notFound = ServerResponse.notFound().build();
		Mono<Forex> forex = repository.findById(forexId);
		Mono<ForexVo> forexVo = Mono.from(forex.map(f -> {
			return new ForexVo(f.getId(), f.getFromCur(), f.getToCur(), f.getRateCur());
		}));
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(forexVo, ForexVo.class)
				.switchIfEmpty(notFound);
	}
	public Mono<ServerResponse> addForex(ServerRequest serverRequest) {
		Mono<ForexVo> forexVo = serverRequest.bodyToMono(ForexVo.class);
		Mono<Forex> forex = forexVo.map(f -> new Forex(f.getFromCur(), f.getToCur(), f.getRateCur()))
				.flatMap(repository::save);
		forexVo = Mono.from(forex.map(f -> {
			return new ForexVo(f.getId(), f.getFromCur(), f.getToCur(), f.getRateCur());
		}));
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(forexVo, ForexVo.class);
	}
	public Mono<ServerResponse> updateForex(ServerRequest serverRequest) {
		Mono<ForexVo> forexVo = serverRequest.bodyToMono(ForexVo.class);
		Mono<Forex> forex = forexVo.map(f -> new Forex(f.getId(), f.getFromCur(), f.getToCur(), f.getRateCur()))
				.flatMap(repository::save);
		forexVo = Mono.from(forex.map(f -> {
			return new ForexVo(f.getId(), f.getFromCur(), f.getToCur(), f.getRateCur());
		}));
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(forexVo, ForexVo.class);
	}
	public Mono<ServerResponse> deleteForex(ServerRequest serverRequest) {
		Mono<ForexVo> forexVo = serverRequest.bodyToMono(ForexVo.class);
		return forexVo.flatMap(f -> repository.findById(f.getId())).flatMap(f -> repository.delete(f))
				.then(ServerResponse.ok().build(Mono.empty()));
	}
}

ServerRequest gives access to various HTTP request elements: the method, URI, query parameters, and — through the separate ServerRequest.Headers interface — the headers.

Similarly, ServerResponse provides access to the HTTP response. Since it is immutable, you create a ServerResponse with a builder. The builder allows you to set the response status, add response headers, and provide a body.

Remember your operations, such as, save, delete etc. will not persist into MongoDB if these are not part of the reactive chain. If operations are not chained, nothing will subscribe to that part of your code and it won’t be executed.

Creating Router Functions

This is another important function of reactive stream as previously mentioned while writing Router Function.

Incoming requests are routed to handler functions with a RouterFunction, which is a function that takes a ServerRequest, and returns a Mono<HandlerFunction>. If a request matches a particular route, a handler function is returned; otherwise it returns an empty Mono.

Given the ForexHandler we showed above, we can now define a router function that routes to the respective handler functions.

We use method-references to refer to the handler functions.

package com.roytuts.spring.boot.reactive.mongodb.crud.router;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.roytuts.spring.boot.reactive.mongodb.crud.handler.ForexHandler;
@Configuration
public class ForexRouter {
	@Bean
	public RouterFunction<ServerResponse> route(ForexHandler handler) {
		return RouterFunctions
				.route(RequestPredicates.GET("/forex").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
						handler::getForexList)
				.andRoute(
						RequestPredicates.GET("/forex/{id}").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
						handler::getForexById)
				.andRoute(RequestPredicates.POST("/forex").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
						.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)), handler::addForex)
				.andRoute(RequestPredicates.PUT("/forex").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
						.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)), handler::updateForex)
				.andRoute(RequestPredicates.DELETE("/forex").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
						.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)), handler::deleteForex);
	}
}

Besides router functions, you can also compose request predicates, by calling RequestPredicate.and(RequestPredicate) or RequestPredicate.or(RequestPredicate).

These work as expected: for and the resulting predicate matches if both given predicates match; or matches if either predicate does. Most of the predicates found in RequestPredicates are compositions.

Notice how we have used lambda expression to functional interfaces in the above Router Functions, that’s why the title is given (with Functional) as Spring Boot MongoDB Functional Reactive CRUD Example.

Creating Main Class

Creating a main would be sufficient to deploy our application into the embedded server.

This is a great advantage that you just need to let Spring know that it is your Spring Boot Application using @SpringBootApplication and main class.

We also need to tell Spring where our entity class and reactive repository interface.

package com.roytuts.spring.boot.reactive.mongodb.crud.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@EntityScan("com.roytuts.spring.boot.reactive.mongodb.crud.entity")
@EnableReactiveMongoRepositories("com.roytuts.spring.boot.reactive.mongodb.crud.repository")
@SpringBootApplication(scanBasePackages = "com.roytuts.spring.boot.reactive.mongodb.crud")
public class ReactiveForexApp {
	public static void main(String[] args) {
		SpringApplication.run(ReactiveForexApp.class, args);
	}
}

Running the Application

Run the main class to start the application. The application will start on embedded server’s port 8080.

Make sure your MongoDB server is running.

Testing the Application

We will use here REST Client (Chrome or Firefox) to test our Spring Reactive Programming.

As we know that we don’t have any data into the database so we will first create new website and store into database.

Add a header – Content-Type: "application/json" while you are performing operations on POST/PUT/DELETE methods.

Creating Forex

Request Method – POST

Request URL – http://localhost:8080/forex

Request Body

{
  "fromCur":"USD",
  "toCur":"IND",
  "rateCur":70
}

Response

{"id":"5d25c4988cf0d142a8a71c0e","fromCur":"USD","toCur":"IND","rateCur":70.0}

Reading Forex

All Forexes

Request Method – GET

Request URL – http://localhost:8080/forex

Response

[{"id":"5d25c4988cf0d142a8a71c0e","fromCur":"USD","toCur":"IND","rateCur":70.0}]
Single Forex

Request Method – GET

Request URL – http://localhost:8080/forex/5d25c4988cf0d142a8a71c0e

Response

{"id":"5d25c4988cf0d142a8a71c0e","fromCur":"USD","toCur":"IND","rateCur":70.0}

Updating Forex

Request Method – POST

Request URL – http://localhost:8080/forex

Request Body

{
  "id": "5d25c4988cf0d142a8a71c0e",
  "fromCur":"USD",
  "toCur":"IND",
  "rateCur":71
}

Response

{"id":"5d25c4988cf0d142a8a71c0e","fromCur":"USD","toCur":"IND","rateCur":71.0}
Verifying Update

Request Method – GET

Request URL – http://localhost:8080/forex

Response

[{"id":"5d25c4988cf0d142a8a71c0e","fromCur":"USD","toCur":"IND","rateCur":71.0}]

Deleting Forex

Request Method – DELETE

Request URL – http://localhost:8080/forex

Request Body

{
  "id": "5d25c4988cf0d142a8a71c0e"
}

Response – 200 OK.

That’s all.

Source Code

download source code

Thanks for reading.

1 thought on “Spring Boot MongoDB Functional Reactive CRUD Example

Leave a Reply

Your email address will not be published. Required fields are marked *