Building a Reactive RESTful Web Service using Spring Boot and Postgres

Suman Das
10 min readMay 3, 2020

--

Spring Boot 2 with Reactor

In this tutorial, we will learn how to use the Spring WebFlux framework along with Spring Data R2DBC to build, test, and consume a simple reactive REST application.

To begin with, let us understand the following:

  • Why Reactive Programming?
  • What is Reactive Programming?
  • Spring WebFlux Framework
  • What is R2DBC?

Why Reactive Programming?

We are living in a world of microservices. To build a simple application we use multiple microservices, which interact with each other. We also have to meet expectations such as scalability, resource utilization, latency, and so on. When we talk about microservices, Spring MVC is one of the most frequently used industry-standard web development framework, which is used to create scalable and extensible microservices.

In traditional Spring MVC applications, when a server receives a request, a servlet thread is created. It delegates the request to the worker threads for I/O operations such as database access, REST API call, and so on. While the worker threads are busy executing the request, the servlet thread (request thread) continues to be in the waiting status and gets blocked. It is also called synchronous request processing, which works on a thread per request model.

Spring MVC Request/Response Flow

In the above model, concurrency is handled by creating a new thread from thread pool for each request. But only a finite number of threads can be created, as each thread consumes separate memory. We can increase the thread pool size, but it will consume additional memory, and if we keep the thread pool size low, then we cannot scale. To counteract this situation, we can either increase the memory of our application or we can run multiple instances of our application behind a load balancer. Both options will help us to scale but will also increase the cost. So the question arises — How can we serve more requests with fewer threads? Reactive Programming is one of the ways by which we can overcome the above limitation. To deal with the drawbacks of Spring MVC based microservices we can use Reactive Spring based microservices.

What is Reactive Programming?

In simple terms, reactive programming is about non-blocking applications that are asynchronous, event-driven, and require a small number of threads to serve the request. It is built around a publisher-subscriber pattern. In the reactive style of programming, we initiate a request for the resource and move on to performing other tasks. When the data is available, we receive the notification along with the data to inform the caller. In the callback function, we can handle the response as per the application requirement. Reactive code does more work with fewer resources. With reactive processing, we can satisfy more concurrent users with fewer microservice instances.

Now that we know what is reactive programming and how it can improve the traditional REST API design, we can proceed to use reactive programming along with Spring.

Spring WebFlux Framework

Spring WebFlux is an alternative to the traditional Spring MVC. Spring Framework 5 includes a new spring-webflux module. Spring WebFlux is a non-blocking web framework built from the ground up to take advantage of multi-core, next-generation processors and handle a large number of concurrent connections. It internally uses Project Reactor and its publisher implementations: Flux and Mono.

It supports two programming models:

  • Annotated Controllers: Consistent with Spring MVC and based on the same annotations from the spring-web module. Both Spring MVC and WebFlux controllers support reactive (Reactor and RxJava) return types, and, as a result, it is not easy to tell them apart. One notable difference is that WebFlux also supports reactive @RequestBody arguments.
  • Functional Endpoints: Lambda-based, lightweight, and functional programming model. You can think of this as a small library or a set of utilities that an application can use to route and handle requests. The big difference with annotated controllers is that the application is in charge of request handling from start to finish versus declaring intent through annotations and being called back.

Here, we’re going to be focusing on the Annotation-based reactive components model.

Reactive Request Processing

What is R2DBC?

R2DBC stands for Reactive Relational Database Connectivity, an incubator to integrate relational databases using a reactive driver. Spring Data R2DBC provides familiar Spring abstractions and repository support for R2DBC. It helps Spring-powered applications to perform database operations in a reactive way. At this moment, only PostGres, MSSQL, and H2 support R2DBC drivers.

Creating a Reactive REST User Management Application

Let us create a simple Reactive REST User Management application using the Spring Webflux framework and Spring Data R2DBC.

Prerequisites

Before we begin, we must ensure that the following pre-requisites are installed on the local machine:

Steps to Build

  1. Set up the Spring WebFlux Project Setup
  2. Include Additional Maven Dependencies
  3. Furnish the Configuration of Postgres and H2
  4. Define a Model Class
  5. Create a Repository
  6. Define the Rest Controller Endpoints
  7. Construct the Service Layer
  8. Test the Application with WebTestClient and Swagger-UI
  9. Consume a Reactive API Using Web Client
  10. Conclusion

1. Set up the Spring WebFlux Project

For this tutorial, we are using JDK 1.8 and Spring Boot 2.3.0.RC1 project. Use start.spring.io and create a sample project using the below artifacts:

  • Spring Reactive Web: The spring reactive web provides a reactive feature to our application.
  • Spring Data R2DBC: Provides Reactive Relational Database Connectivity to persist data in SQL stores using Spring Data in reactive applications.
  • Lombok: Java annotation library which helps to reduce boilerplate code.
  • H2 Database: Provides a fast in-memory database that supports JDBC API and R2DBC access, with a small (2mb) footprint. It is required for Integration testing.
  • PostgreSQL Driver: A JDBC and R2DBC driver that allows Java programs to connect to a PostgreSQL database using standard, database independent Java code.
Creating a Sample Project

2. Include Additional Maven Dependencies

Add the additional dependencies, shown below, to our project to enable Swagger for our application.

Note: Swagger 3 will be soon released, and it includes support for netty. Till then we can use the snapshot version.

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-webflux</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>

3. Furnish the Configuration

3.1 Swagger Configuration

Configure the Swagger for our reactive application which will enable us to test our application easily.

Swagger-Config

3.2 Database Related Configuration

We have already added reactive streams Postgres driver in our classpath to perform non-blocking DB operations. Next, use the application.yml file to provide a certain configuration related to Postgres and H2. Also, configure multiple Spring profiles so that we can use different configurations based on the different environments. We will use the Postgres database for Development/Production and the H2 database for Testing.

spring:
profiles:
active: dev
---
spring:
profiles: dev
r2dbc:
url: r2dbc:postgresql://localhost:5432/test
username: postgres
password: postgres
logging:
level:
org.springframework.data.r2dbc: Debug
---
spring:
profiles: test
r2dbc:
url: r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
name: sa
password:

We cannot use the entire Spring Boot functionality with Spring Data R2DBC. We need to perform the following steps to manually add the tables:

a) Using Spring Data R2DBC, tables cannot be created at runtime. We can either create the tables externally or we can create a schema.sql file inside the resources folder and execute them programmatically. For our application, we will create a schema.sql file inside the resources folder with all the DDL statements.

DROP TABLE IF EXISTS users ;
CREATE TABLE users ( id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, age integer,salary decimal);
DROP TABLE IF EXISTS department ;
CREATE TABLE department ( id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,user_id integer, name VARCHAR(100) NOT NULL, loc VARCHAR(100));

b) Execute the schema.sql file, for which we need to override the ConnectionFactoryInitializer Bean.

CustomConnectionFactoryInitializer

4. Define a Model Class

Create two domain objects User and Department. The User entity has an ID, Name, Age, and Salary. We will use the User entity to test the User Management application. The entity classes are annotated with “@Table” annotation to identify a domain object to be persisted in the Database. Also, we have added @Id for each entity to define the primary keys of the table.

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("users")
public class User {

@Id
private Integer id;
private String name;
private int age;
private double salary;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("department")
public class Department {
@Id
private Integer id;
private String name;
@Column("user_id")
private Integer userId;
private String loc;
}

5. Create a Repository

Create a JPA UserRepository using which we can perform all the database related activities. It will serve as a data repository that supports non-blocking reactive streams. The UserRepository interface extends ReactiveCrudRepository which provides, for example, basic CRUD functionality. Spring Boot automatically plugs in an implementation of this interface at runtime.

We have added one custom query to fetch the users based on their age.

public interface UserRepository extends ReactiveCrudRepository<User,Long> {
@Query("select * from users where age >= $1")
Flux<User> findByAge(int age);
}

Create another JPA repository for the Department entity.

public interface DepartmentRepository extends ReactiveCrudRepository<Department,Integer> {
Mono<Department> findByUserId(Integer userId);
}

6. Define the Rest Controller Endpoints

Finally, let us write some APIs that can be exposed to the clients. The RestControlller publishes reactive streams of User.

UserController

All the controller endpoints return a Publisher in the form of a Flux or a Mono. As we can see from the above code that, if we want to return a single resource to the caller then we need to use Mono and if we want to return a collection of resources then we need to use Flux.

7. Construct the Service Layer

Let us construct the Service Layer for our application. Service Layer is required for encapsulating the business logic and also for centralizing the data access.

UserService

In this example, UserService does not have much business logic other than interacting with the Postgres Database using reactive Repository.

But we can also have a look at the below methods where we are performing parallel operations.

7.1 Parallel calls to fetch the data of the same type

public Flux<User> fetchUsers(List<Integer> userIds) {
return Flux.fromIterable(userIds)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(i -> findById(i))
.ordered((u1, u2) -> u2.getId() - u1.getId());
}

Here, we are trying to fetch the details of multiple users simultaneously and return the result as a list of users. After creating a Flux from the list of userIds, it calls the parallel method, which internally creates ParallelFlux this indicates parallel execution. Here, we have decided to use the elastic scheduler to run the call on, but we could have chosen any other configuration. Next, we invoke flatMap to run the findById method, which returns ParallelFlux. Finally, we need to specify how to convert ParallelFlux to simple Flux. Hence, we have used an ordered method with a custom comparator.

7.2 Parallel calls to fetch the data of the different type

Mono<User> user = findById(userId).subscribeOn(Schedulers.elastic());
Mono<Department> department = getDepartmentByUserId(userId).subscribeOn(Schedulers.elastic());
return Mono.zip(user, department, userDepartmentDTOBiFunction);

There are cases where we may want to merge the results of multiple database/API calls and return the result to the caller. In such cases, the Mono class provides the static zip method, which lets us combine two or more results. Since the subscribeOn method does not subscribe to the Mono, we are using Scheduler. Again we are using the elastic scheduler only which ensures each subscription happens on a dedicated single thread.

8. Test the Application with WebTestClient and Swagger-UI

We will use WebTestClient to perform integration testing for our REST APIs.

UserControllerTest

We can also use Swagger-UI to test our application. The following results are displayed based on our application.

Swagger-UI.html

9. Consume a Reactive API Using Web Client

Till now we have seen how to expose reactive APIs. Next, we will see how to consume a reactive API. Here, we will use WebClient to interact with the reactive APIs created earlier. WebClient introduced in Spring 5 is a non-blocking client with support for Reactive Streams. We can create a simple WebClient to retrieve data from our User Management application.

UserClient

10. Conclusion

We just learned the basics of reactive programming with Spring and built a simple Restful service using the Spring WebFlux framework and Spring data R2dbc that supports reactive web components. We learned how to use RestController and WebClient to publish and consume reactive streams, respectively. We also learned how to perform integration testing of reactive Rest APIs using WebTestClient. The reactive-stack web framework, Spring WebFlux is not a replacement for the Spring MVC module. Spring MVC module will be part of the Spring ECO system.

If you would like to refer to the full code, do check:

References

--

--