Kind of running around in circles trying to handle a scenario that I might just not be looking at it the "reactive" way of simply need a break to see the bigger picture.
Lets say I want to save a user to the database but first I need to see if a person with that name exists, so:
Repository using ReactiveCrudRepository
interface UserRepository : ReactiveCrudRepository<User, Long> {
@Query("SELECT * FROM user WHERE name = :name LIMIT 1")
fun findByName(name: String): Mono<User>
fun saveUser(user: User): Mono<User>
}
Business code
fun saveUser(user: User): Mono<User> {
return repository.findByName(user.name)
.flatMap { existingUser ->
return Mono.error(UserExistsException()) //A Mono<User> is expected
}
.switchIfEmpty {
return repository.saveUser(user)
}
}
The fact that the repository returns Mono.empty() on nulls (that is, if no user with that name is found) seems to limit the way I can handle the case where the user already exists, because I wanted to throw the error so the caller deals with this in his way.
I tried to return a Mono<Optional<User>>
on the repository side so I never get an empty
but the library does not allow this.
I'm new to reactive in general and I wanted to keep things simple but also within the good practices, so how should I approach this? Is flatMap
what I want here? I need to return a User on success and ideally an exception on failures, but I can't simply throw inside flatMap
since it asks for a Mono<User>
In Kotlin, unlike Java, return
is not used inside lambdas. This is only allowed inside inline
lambdas for early return.
Besides that there are two more issues with your code:
switchIfEmpty
accepts a Mono
value, but not a lambda.Mono.error
requires a generic argument of T
.So here is the fixed code:
fun saveUser(user: User): Mono<User> =
repository.findByName(user.name)
.flatMap { existingUser ->
Mono.error<User>(UserExistsException())
}.switchIfEmpty(repository.saveUser(user))
If you're working with Kotlin however with Spring, it's recommended to use Coroutines and avoid Project Reactor if possible. To do so we can redefine the UserRepository
using CoroutineCrudRepository
instead of ReactiveCrudRepository
.
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
interface UserRepository : CoroutineCrudRepository<User, Long> {
@Query("SELECT * FROM user WHERE name = :name LIMIT 1")
suspend fun findByName(name: String): User?
suspend fun saveUser(user: User): User
}
The resulting code also looks much nicer.
suspend fun saveUser(user: User): User {
val foundUser = repository.findByName(user.name)
return if (foundUser == null) repository.saveUser(user)
else throw UserExistsException()
}