5 common mistakes of Webflux novices

Nikesh Shetty
4 min readAug 19, 2019

--

This post is based on observations of working on codebase using spring webflux. There is no universal definition of right vs not-so right way, but following the programming semantic guidelines will make the code maintainable and free from run-time surprises .

Use the right tool in right way for right job at the right time.

Spring webflux is a webstack which provides ability for reactive programming. The version of spring-webflux at the time of writing this blog was 2.1.7 with Kotlin

1. Difference between map and flatMap

Yes there is a difference between a flatmap and map.

flatMap should be used for non-blocking operations, or in short anything which returns back Mono,Flux.

map should be used when you want to do the transformation of an object /data in fixed time. The operations which are done synchronously.

eg: In the below example, we have used flatMap for saving person in database, because it’s an async operation for which time is never deterministic, while for converting the person into EnhancedPerson object, we have used map, because it’s a blocking and synchronous operation for which time taken is always going to be deterministic.

return Mono.just(Person("name", "age:12"))
.map { person ->
EnhancedPerson(person, "id-set", "savedInDb")
}.flatMap { person ->
reactiveMongoDb.save(person)
}

2. Using nested flatMap/map

Most of the programmers with imperative programming mindset often end up writing a lot of code in map/flatMap. If you ever see a flatMap/map operation nested inside another flatMap/map or a map operation it’s a code smell. For example in the below code we see 2 nested flatmap operations which makes it very difficult to read.

fun makePersonASalariedEmployee(personId: String): Mono<Person> {

return personsRepository.findPerson(personId)
.flatMap { person ->
employeeService.toEmployee(person)
.flatMap { employee ->

salariedEmployeService.toSalariedEmployee(employee)
}
}
}

The same code can be transformed by following our age old time tested Unix rule:

Do One Thing and Do It Well

Inside every flatMap/map restrict yourself to transform/process data only once. And if you ever need more transformations, then that’s not and never going to be in the jurisdiction of the current flatMap/map, rather let the data flow and attach a separate flatmap/map transformers in the chain. It’s good to treat every flatMap/map with Single responsibility principle.

fun makePersonASalariedEmployee(personId: String): Mono<Person> {

return personsRepository.findPerson(personId)
.flatMap { person ->
employeeService.toEmployee(person)
}
.flatMap { employee ->
salariedEmployeService.toSalariedEmployee(employee)
}
}

3. Utilizing reactor library functions like filter, switchIfEmpty, onErrorReturn……

A lot of imperative style programming can be ridden off by using these powerful chain functions. In the below example we can get rid of conditional branching and simply use filter function to decide further outcome.

return Mono.just(Person("name", false))
.flatMap { person ->
if (person.consentToSave)
reactiveMongoDb.save(person)
Mono.empty()

}
...The above code..........|........can be transformed............
...elegantly...............|.........using filter function........
....as shown ..............V.......in..the below code............
return Mono.just(Person("name", false))
.filter { person ->
person.consentToSave
}
.flatMap { person ->
reactiveMongoDb.save(person)
}

4. Beware of switchIfEmpty

<Update as on 16th August 2020> You can safely ignore this 4th point. The current reactor library has no surprises in terms of usages.

You can use both the code snippets and have same behavior.

T̵h̵e̵ ̵c̵o̵d̵e̵ ̵t̵h̵a̵t̵ ̵y̵o̵u̵ ̵e̵x̵e̵c̵u̵t̵e̵ ̵i̵n̵ ̵s̵w̵i̵t̵c̵h̵I̵f̵E̵m̵p̵t̵y̵ ̵i̵s̵ ̵e̵x̵p̵e̵c̵t̵e̵d̵ ̵t̵o̵ ̵b̵e̵ ̵w̵i̵t̵h̵o̵u̵t̵ ̵s̵i̵d̵e̵-̵e̵f̵f̵e̵c̵t̵s̵.̵ ̵U̵n̵f̵o̵r̵t̵u̵n̵a̵t̵e̵l̵y̵ ̵t̵h̵e̵ ̵d̵o̵c̵u̵m̵e̵n̵t̵a̵t̵i̵o̵n̵ ̵d̵o̵e̵s̵ ̵n̵o̵t̵ ̵m̵e̵n̵t̵i̵o̵n̵ ̵i̵t̵,̵ ̵a̵t̵ ̵t̵h̵e̵ ̵t̵i̵m̵e̵ ̵o̵f̵ ̵w̵r̵i̵t̵i̵n̵g̵ ̵t̵h̵i̵s̵ ̵b̵l̵o̵g̵.̵ ̵s̵w̵i̵t̵c̵h̵I̵f̵E̵m̵p̵t̵y̵ ̵d̵o̵e̵s̵ ̵e̵a̵g̵e̵r̵ ̵c̵o̵m̵p̵u̵t̵a̵t̵i̵o̵n̵ ̵o̵f̵ ̵t̵h̵e̵ ̵v̵a̵l̵u̵e̵ ̵p̵r̵o̵v̵i̵d̵e̵d̵ ̵t̵o̵ ̵i̵t̵.̵ ̵S̵o̵ ̵i̵n̵ ̵t̵h̵e̵ ̵b̵e̵l̵o̵w̵ ̵e̵x̵a̵m̵p̵l̵e̵,̵ ̵e̵v̵e̵n̵ ̵i̵f̵ ̵y̵o̵u̵ ̵f̵i̵n̵d̵ ̵t̵h̵e̵ ̵o̵r̵d̵e̵r̵ ̵i̵n̵ ̵O̵r̵d̵e̵r̵s̵D̵B̵,̵ ̵i̵t̵ ̵w̵i̵l̵l̵ ̵s̵t̵i̵l̵l̵ ̵s̵a̵v̵e̵ ̵i̵n̵ ̵M̵i̵s̵s̵i̵n̵g̵O̵r̵d̵e̵r̵s̵D̵B̵ ̵d̵u̵e̵ ̵t̵o̵ ̵i̵t̵’̵s̵ ̵e̵a̵g̵e̵r̵ ̵c̵o̵m̵p̵u̵t̵a̵t̵i̵o̵n̵.̵

return ordersDbRepo.findById("order-id")
.switchIfEmpty(
missingOrdersDb.save(Order("order-id", false))
)

One way is to use the defer keyword inside switchIfEmpty

return ordersDbRepo.findById("order-id")
.switchIfEmpty(
Mono.defer { missingOrdersDb.save(Order("order-id", false)) })

There is also another way to solve it as shown in below code snippet. Notice the switchIfEmpty is an extension function and can be accessed using the import as shown below. Extension function feature is specific to Kotlin.

import reactor.core.publisher.switchIfEmpty...return ordersDbRepo.findById("order-id")
.switchIfEmpty { missingOrdersDb.save(Order("order-id", false)) }

5. Using Reactive components as far as possible.

The benefit of reactive programming can be acquired when all of your components for async operations are written in reactive code style.

Code your interfaces to return a Publisher (Mono/Flux).

Flux.range(1, 10)
.map {
doSomeIoOperation(it)
}
fun doSomeIoOperation(number: Int): Int {
// some io operation
}
*******The below doSomeIoOperation expects IO to be an asyc task and should be the preferred way of writing in reactive way****Flux.range(1, 10)
.flatMap {
doSomeIoOperation(it)
}
fun doSomeIoOperation(number: Int): Mono<Int> {
// some io operation
}

Coding to interfaces, not implementation

Although reactive webflux is not mature enough to have support for all async operations in a non-blocking manner, but designing our code to Publishers will pave for an easier integration, if ever the support comes for these operations.

Another good example will be using webClient(reactive) over restTemplate while making a http call.

Any other patterns/tricks that you think should be shared? Please do comment!

P.S: Thanks Loveneesh Singh for the reviews.

--

--