The traditional servlet based Spring MVC stack spawns a thread for every incoming request. This overhead is necessary, since the thread gets blocked by any I/O operation performed by the servlet. Spring 5 has introduced support for WebFlux which is a fully asynchronous and non-blocking reactive web stack.
Several performance tests ( 1 2 3 ) have shown that the non-blocking approach can increase request/response throughput by factors, while keeping CPU utilization and memory footprint on comparable levels.
WebFlux is based on project Reactor and uses its publisher implementations, Flux and Mono. In order to develop, debug and test WebFlux applications, developers need to learn a functional programming model which is quite different from the imperative model used in traditional Spring MVC applications.
As an alternative to learning WebFlux, Spring 5 also offers the option to build reactive web applications using Kotlin or more specifically Kotlin Coroutines. For readers who know JavaScript's async/await concept, this comparison with Kotlin's suspend functions helps to understand the concept.
Using a Task-Manager application this project demonstrates how to build a reactive web application with Kotlin and Spring in the backend. It intentionally exceeds the scope of a Hello World application and contains a React frontend written in Typescript.
In order to build and run the task-manager application first make sure that the following software is installed:
- docker
- docker-compose
- Java Development Kitt version 11 (e.g. OpenJDK 11)
- node.js version 12
- yarn
The tasks are persisted in a postgres database. It can be started with the following commands in the project directory:
cd docker
docker-compose up
This command also starts adminer, which is a web based database administration tool available under http://localhost:8090.
Use the following attributes for logging in:
- Database System: PostgreSQL
- Server: db
- User: postgres
- Password: example
- Database: postgres
From the project directory build and start the backend using gradle:
./gradlew bootRun
Upon start the backend automatically creates the required database schema. Task-Manager uses swagger to automatically document its API. Swagger can be viewed under http://localhost:8080/swagger-ui/index.html.
Build and start the frontend using yarn:
cd task-browser
yarn build
yarn start
The frontend is reachable under http://localhost:3000
The folder task-service
contains the Task-Manager backend.
The non-blocking web application requires, that all I/O operations are non-blocking, too. This means the conventional jdbc database drivers cannot be used. Instead r2dbc drivers are required. Databases that support R2DBC include Postgres, H2, and Microsoft SQL Server. Oracle currently does not support R2DBC.
The backend is inspired by the project
webflux-r2dbc-kotlin.
It utilizes spring-data-r2dbc
to wrap the r2dbc driver and provide reactive
data repositories.
However, the webflux-r2dbc-kotlin project still relies on Flux/Mono in the repository.
Since Spring 5.2 it is now possible to extend CoroutineCrudRepository
and return Kotlin
Flows from the repository functions:
@Repository
interface TaskRepository : CoroutineCrudRepository<Task, Long> {
fun findByDescriptionContainingIgnoreCase(description: String): Flow<Task>
@Query("SELECT t.* FROM task t WHERE t.completed = :completed")
fun findByCompleted(completed: Boolean): Flow<Task>
}
The service layer contains suspend
functions that allow an imperative programming model.
@Service
class TaskService(private val taskRepository: TaskRepository) {
...
suspend fun update(id: Long, taskDTO: TaskDTO): Task? {
if(findById(id)==null) return null
return taskRepository.save(taskDTO.toModel(withId = id))
}
...
}
In the API layer it is possible to use the traditional Spring @RestController
and
handler methods that return Flows, too.
@RestController
@RequestMapping("/api")
class TaskController(private val taskService: TaskService, private val userService: UserService) {
...
@GetMapping("/task")
suspend fun findAll(): Flow<Task> {
return taskService.findAll()
}
...
}
Unfortunately the Coroutine support is not yet available in all Spring projects.
As pointed out in this
issue,
Spring Security currently does not support annotations like @PreAuthorize
with handlers returning Flow
.Mono
or Flux
are still required, here.
The folder task-browser
contains the Task-Manager React frontend.
It is inspired by the
react-crud-web-api project
but has been rewritten in Typescript.
For a detailed explanation refer to the
project webpage.