Skip to content

owahlen/task-manager

Repository files navigation

Building reactive Web applications with React, Kotlin and Spring

Introduction

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.

Screenshot of Task-Manager

Building and Running the Task-Manager Application

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

1. Start the Postgres Database

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.

Adminer

Use the following attributes for logging in:

  • Database System: PostgreSQL
  • Server: db
  • User: postgres
  • Password: example
  • Database: postgres

2. Build and start the Spring backend

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.

Swagger-UI

3. Build and start the React frontend

Build and start the frontend using yarn:

cd task-browser
yarn build
yarn start

The frontend is reachable under http://localhost:3000

Task-Manager backend

The folder task-service contains the Task-Manager backend.

Database Layer

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>

}

Service Layer

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))
    }
    ...
}

API Layer

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()
    }
    ...
}

Caveats

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.

Task-Manager frontend

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.

About

REST service built with Spring, WebFlux, Kotlin Coroutines, and R2DBC

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published