I'm using Spring Boot with Kotlin's Coroutines. I ...
# coroutines
d
I'm using Spring Boot with Kotlin's Coroutines. I have created a validator annotation. Like:
Copy code
@get:UniqueEmail
  val email: String,
but ConstraintValidator are not suspendable. So I have to run it blocking like :
Copy code
@Component
class UniqueEmailValidator @Autowired constructor(
    private val userService: UserService
) : ConstraintValidator<UniqueEmail, String> {
    override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean {
        if (value == null) return true
        // Run suspend function in a blocking way for validation
        return runBlocking { userService.findByEmail(value) == null }
    }
}
will that runBlocking somehow breaks (slows) all the coroutines afterward. I mean all the coroutine code that comes after that ? The repo is here: https://github.com/danygiguere/spring-boot-3-reactive-with-kotlin-coroutines
r
Let me check if I understand correctly: • You have a non-
suspend
function that is called by the framework itself, and this function cannot be
suspend
because of the framework • The internal logic has something to it which involves just waiting (such as a database query) and so is declared as a
suspend
function Is that correct? In this case I'm fairly certain that
runBlocking
will use
Dispatchers.Default
to run the suspending code within, which means everything ought to work as expected in those coroutines. You do of course have the current/parent thread which is sitting blocked, but it's not using any CPU so I don't think it's hurting anything unless it came from `Dispatcher.Default`'s threadpool
👍 1
d
yes, the validator is non-suspend as designed by the framework. Inside the validator I need to do a suspend call (query a database).
ok this ran in 3025ms. I think it proves runBlocking does not block what happens after:
Copy code
@GetMapping("/demo/parallel")
    suspend fun demoParallel(exchange: ServerWebExchange): String = coroutineScope {
        val timeBefore = System.currentTimeMillis()
        // The 2 calls below are executed in parallel (at the same time)
        runBlocking {
            executeFaked1000msCall()
            executeFaked1000msCall()
        }
        val durationRequest1 = async{executeFaked1000msCall()}
        val durationRequest2 = async{executeFaked1000msCall()}
        val durationRequest3 = async{executeFaked1000msCall()}
        val durationRequest4 = async{executeFaked1000msCall()}
        durationRequest1.await()
        durationRequest2.await()
        durationRequest3.await()
        durationRequest4.await()
        val timeAfter = System.currentTimeMillis()
        val duration = abs(timeBefore - timeAfter)
        return@coroutineScope "Number of milliseconds to execute this function (containing two 1000ms parallel queries) : $duration ms"
    }
The runBlocking calls ran in 2 secs. All other ran in parallel.... in 1 sec.
r
runBlocking
shouldn't ever be used within a
suspend
function, since it starves the thread and you could just do without it. In your original scenario, would you mind printing the name of the thread that the validator function is running on? Before touching
runBlocking
put in
println("${Thread.currentThread().name}")
d
Copy code
@Component
class UniqueEmailValidator @Autowired constructor(
    private val userService: UserService
) : ConstraintValidator<UniqueEmail, String> {
    override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean {
        if (value == null) return true
        // Run suspend function in a blocking way for validation
        println("****** * : ${Thread.currentThread().name}")
        return runBlocking { userService.findByEmail(value) == null }
    }
}
****** * : reactor-http-nio-3
r
So that thread that's getting blocked isn't from `Dispatchers.Default`'s threadpool, so I'd say you're good to go
👍 1
d
in that case I have no choice to use the runBlocking. Else I see : Suspend function 'suspend fun findByEmail(email: String): UserDto?' can only be called from a coroutine or another suspend function
r
Yeah, I think
runBlocking
is actually the right choice here. Its purpose is to go from a blocking context to a suspending context (and wait for completion), which is exactly what you have here.
👍 1
a
I would suggest you to think to move this logic to service layer. I think this kind of checks belongs more to domain than to the presentation layer. According to your example, there can be a case when you use the service from some component which has no connection to the controller (job, listener, etc.). In this case you can have a problem with uniqueness of the email. If you move the check to service than your problem will disappear. 😉
☝️ 1
d
The email field with the annotation is in a RegisterRequest that I use only in the controller. Anywhere else I’ll use the UserDto which doesn’t have the UniqueEmail annotation. Does it make sense?
a
I’m not sure about your proposal. Maybe you can push some commit to your repo. So we can see it. 🙂
d
Also, I prefer to use it directly in a Request object that does the validation of the request as soon as it enters the framework. That way I can throw all errors to the client, all at once. If I throw errors in services, the client will have to fix the first set of errors before reaching a second service that could throw another set of errors.
a
Well, it is up to you. I think there is no such a "best practice" to do all validations before the processing of the request. :) If this is critical for your app, so you should live with
runBlocking
or so... Think about a possibility to return some specific status code (409 or 422) for checks from service layer. ;) In my practice I saw a lot of tries to solve such a problem, and none of them were successful. If your application is not
hello world
-like, you'll find a difficulties with DB transactions, integration with other systems and so on...
👍 1
d
I'm keeping my fingers crossed. I want to start a new project with Spring Boot and Kotlin. I love Kotlin. The Request object (where everything is validated first thing when the request hits the app), I'v learned it from Rail, Laravel and Adonis.js. With those framework, it's built it and works like a charm 🙂
👍 1