Reactive Programming with Kotlin and Spring WebFlux : Part 2

Jyoti
9 min readApr 15, 2023

--

In this article we will see how to :

  1. set-up project in IntelliJ for making reactive rest api with Kotlin and spring webflux
  2. Making a music player project using reactive programming with kotlin and spring webflux

Pre-requisite: basic knowledge about kotlin and spring framework

1. Project set-up

Starting with Spring Initializr

To manually initialize the project:

i) Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

ii) Choose Gradle and the language as kotlin.

iii) choose any spring boot version

iv) Click Dependencies and select Spring Reactive Web (build reactive web application with spring webflux and netty), Spring Data R2DBC (it will help to connect to reactive relational database), PostgresSQL Driver (a jdbc and R2DBC driver that allows application to connect to postgresSQL database)

v) Click Generate.

vi) A zip file will be downloaded. Extract that zip file and open with intellij.

project setup

Also, we will be needing PostgreSQL database. You can download manually from google and set it up. We will be using docker image of database here. For detailed setup of docker with postgresdb you can refer this.

Open project in intellij and open terminal there.

We are using colima here. Install colima with this command:

brew install colima 

Docker client is required for Docker runtime. Install it with brew

brew install docker

Now you can use the docker client on macOS after colima start with no additional setup.

colima start

You can check existing docker image if any there with “docker ps” command.

Create postgres database with following commands:

  1. Execute colima start
  2. Execute
docker run --name postgresdb1 -e POSTGRES_DB=musicplayerreactive -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres

3. Run brew install psqlodbc

4. Run docker ps — The output has the container name and id

5. Run docker exec -it <container name/id> /bin/bash

6. Run psql -U postgres -d musicplayerreactive

7. Run this query now to create songs table.

CREATE TABLE songs

(

ID BIGINT GENERATED BY DEFAULT AS IDENTITY,

NAME VARCHAR(50) NOT NULL,

COMPOSER VARCHAR(50) NOT NULL,

LANGUAGE VARCHAR(50) NOT NULL,

RELEASE_DATE DATE NOT NULL,

PRIMARY KEY (ID)

);

2. Making a music player project using reactive programming with Kotlin and spring webflux

We are creating music player application using reactive programming. In this application we will be having different endpoints for

i) getting all songs from database

ii) getting song with particular id from database

iii) posting (adding new song to database)

iv) updating the song with particular id

v) deleting song with particular id from database

whole code can be found here.

As we know controller is the main entry point where the request comes.

This is how code for MusicPlayerController looks like:

package com.musicplayerreactive.musicplayerreactive.controller

import com.musicplayerreactive.musicplayerreactive.model.MusicPlayerModel
import com.musicplayerreactive.musicplayerreactive.model.SongRequest
import com.musicplayerreactive.musicplayerreactive.services.MusicPlayerService
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import javax.validation.Valid

@RestController
@RequestMapping("/songs")
class MusicPlayerController(val musicPlayerService: MusicPlayerService) {

@GetMapping
fun getAllSongs(): Flux<MusicPlayerModel> {
return musicPlayerService.getAllSongs()
}

@GetMapping("/{id}")
fun findSongById(@PathVariable id:Int): Mono<MusicPlayerModel> {
return musicPlayerService.findById(id)
}

@PostMapping
fun save(@Valid @RequestBody songRequest: SongRequest): Mono<MusicPlayerModel> {
return musicPlayerService.save(songRequest)
}

@PutMapping("/{id}")
fun updateSong(@Valid @RequestBody songRequest: SongRequest, @PathVariable id:Int): Mono<MusicPlayerModel>{
return musicPlayerService.updateSong(id, songRequest)
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteSong(@PathVariable id:Int): Mono<Void>{
return musicPlayerService.deleteSong(id)
}

}

This is just like normal springboot mvc application controller. The annotation @ RestController is used to create controllers for REST APIs which can return JSON. The annotation @ Request Mapping is used to map web requests. This means all the web request having /songs mapping will be directed to this controller. @ Request Mapping annotation can be applied to class-level and method-level in a controller.

Also, we the annotation @ GetMapping , @ PostMapping, @ PutMapping, @ DeleteMapping are shortcut for @ RequestMapping(method = RequestMethod.GET), POST, PUT and DELETE.

Let’s see all function one-by-one from controller:

One thing to note controller doesn’t do any operation or manipulation of data. It just forward the incoming request of particular mapping to service and service does the operation.

  1. the fun getAllSongs return all songs by calling MusicPlayerService get All songs method. All get request that end with /songs will come to this functions.
  2. the fun findSongById return song having provided id in url by calling MusicPlayerService findById method. Get request that end with /songs/{id} will come to this functions. If request url have /song/1 then this will return song with id 1.
  3. the fun save, saves the song in database by calling service and return MusicPlayerModel. Post request that end with /songs and have songRequest in request body will come to this function.
  4. the fun updateSong processes all http request having /songs/{id} and having http method as Put.
  5. the fun deleteSong processes all http request having /songs/{id} and having http method as Delete.

So, internally all these controller call services to perform the operation. Here they all interact with different method of MusicPlayerService.

The MusicPlayerService looks like this:

package com.musicplayerreactive.musicplayerreactive.services
import NotFoundException
import com.musicplayerreactive.musicplayerreactive.model.MusicPlayerModel
import com.musicplayerreactive.musicplayerreactive.model.SongRequest
import com.musicplayerreactive.musicplayerreactive.repository.MusicPlayerRepository
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class MusicPlayerService(val repository: MusicPlayerRepository) {

fun getAllSongs(): Flux<MusicPlayerModel> {
return repository.findAll()
}

fun save(songRequest: SongRequest): Mono<MusicPlayerModel> {
return repository.save(
MusicPlayerModel(
name = songRequest.name,
composer = songRequest.composer,
language = songRequest.language,
release_date = songRequest.release_date
)
)
}

fun updateSong(id: Int, songRequest: SongRequest): Mono<MusicPlayerModel> {
return findById(id)
.flatMap { song ->
repository.save(
MusicPlayerModel(
id = id,
name = songRequest.name,
composer = songRequest.composer,
language = songRequest.language,
release_date = songRequest.release_date
)
)
}
}

fun findById(id: Int): Mono<MusicPlayerModel> {
return repository.findById(id)
.switchIfEmpty(
Mono.error(
NotFoundException("song with id $id not found")
)
)
}

fun deleteSong(id: Int): Mono<Void> {
return repository.findById(id)
.flatMap { song ->
repository.deleteById(id
)
}
}
}

@ Service annotation used to define service in spring. All the business logic (like doing some validation and all) are done in service. Please keep in mind business logic and domain logic are two different thing. Domain related logic should be written in domain service and not application service of spring boot (If you know about domain driven design you might know it already :) , else please read about it because good developers should know about domain and domain driven design and should think in terms of domain. I have recently attended a training on it and planning to write about it as well, you can read that as well if you want some basic understanding :P )

So, services here. It has getAllSongs method which get all songs from database. So logically behind the scene it should go in database, run a query to get all songs from database and return us the List of MusicPlayerModel (song with details). But Spring Repository come for rescue. The Repository Pattern separates the data access logic (crud operation and querying database operation) and maps it to the entities in the business logic. The repository.findAll() method does this fetch operation for us .

Similarly all different methods here in service are there.

  1. fun save calls repository.save() method and pass MusicPlayerModel. The model works a container that contains the data of the application. You can say it as a class that map to fields in actual database. Like here MusicPlayerModel class which has id, name, composer, language, release_date i.e referring to the actual schema of the table - songs.
package com.musicplayerreactive.musicplayerreactive.model
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table
import java.time.LocalDate

@Table(name = "songs")
data class MusicPlayerModel(
@Id
val id: Int?=null,
val name: String,
val composer: String,
val language: String,
val release_date: LocalDate,
)

2. fun updateSong() update song by id. It first find the song from database by calling fun findById and then update the song in database by calling repository.save() and passing the new values.

3. fun findById() return the MusicPlayerModel i.e a song details by calling repository.findById().

4. fun deleteSong(id: Int) delete the song from database by first finding the song by that id in database and then calling repository.deleteById() method.

You can see as well if song is not found we are calling Mono.error() and throwing error. This is business logic right! i.e is song is not found throw an error.

Also, the main thing, the black box i.e our repository, which under the hood is fetching or updating or deleting the record from database. The repository code looks like below:

package com.musicplayerreactive.musicplayerreactive.repository
import com.musicplayerreactive.musicplayerreactive.model.MusicPlayerModel
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import org.springframework.stereotype.Repository

@Repository
interface MusicPlayerRepository : ReactiveCrudRepository <MusicPlayerModel, Int> {

}

So, repository is an abstraction over the data access layer of an application. It provides a way to interact with the database or other data storage systems, without having to write the boilerplate code to handle CRUD (Create, Read, Update, Delete) operations.

ReactiveCrudRepository is an interface provided by Spring Data for reactive programming in Spring applications. This Interface provides a set of methods that allow us to interact with a database in a reactive way. It includes methods for finding all entities, finding entities by ID, saving an entity, and deleting an entity. All the available method in these interface are :

methods in ReactiveCrudRepository

You can leverage all these methods directly using object of MusicPlayerRepository without writing code for it. Like in our case we have written repository.findById(id) in services, right? So, it internally uses the findById(ID id) of this ReactiveCrudRepository. You can use any of these methods in your application.

To set up repository you just need to create a interface MusicPlayerRepository and add @Repository annotation over it. Make this interface extend ReactiveCrudRepository and specify the Model (the database structure you are referring to, here it is MusicPlayerModel) and the primary key (here it is Id) in <> . And you have repository ready with you to use.

3. Testing the rest api

Create a file in resources folder name application.yml if its already not present and add below code:


spring:
r2dbc:
url: "r2dbc:postgresql://localhost:5432/musicplayerreactive"
username: postgres
password: postgres


server:
port: 8082
error:
include-message: always

In username and password specify the username and password you used while creating database

(remember we provided that in this command : docker run — name postgresdb1 -e POSTGRES_DB=musicplayerreactive -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres)

Currently we have a table name songs in our database.

Run following command to enter in db and edit the table .

Right now our table is empty. To insert record in table run following command

 INSERT INTO songs(NAME, COMPOSER, LANGUAGE, RELEASE_DATE ) VALUES('song1', 'arijit', 'Hindi', '2022-06-01');
INSERT INTO songs(NAME, COMPOSER, LANGUAGE, RELEASE_DATE ) VALUES('song2', 'arijit', 'Hindi', '2022-07-01');

like this:

Now we have data in our database.

Run our spring boot application using following command:

./gradlew bootRun

Now open your postman and test all api like below:

  1. get request to get all songs

2. get song by id

3. add songs to db

4. update song by id

5. delete song by id

6. final state of db

In next article we will look into how to make http call to other application from our application using spring webclient.

Thank for reading till here. If you have any feedback and suggestions for me please do share.

--

--

Jyoti
Jyoti

Written by Jyoti

Explorer, Observer, Curious and a head full of questions and thoughts.

No responses yet