I am writing a Spring Boot Rest Service in Kotlin. I want to use coroutines as an alternative to Webflux to achieve non-blocking asynchronous behavior. I am using Spring Boot 2.1 and know that I cannot achieve true non-blocking behavior because I am blocking at Controller. However, I am okay with that for now till the time Spring Boot 2.2 is generally available.
My app is three-layered ie Controller->Service->Repository. In repository, I am calling other services ie network calls and have marked the method as suspend.
I want to ensure if this is the right approach, additionally, does call to suspend fun inside ResourceService blocks the caller thread?
Also after reading https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761 by Roman Elizarov I am not sure if I should use withContext along with all of my suspending functions?
package my.springbootapp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import mu.KotlinLogging
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import java.util.concurrent.Executors
val logger = KotlinLogging.logger { }
@SpringBootApplication
class App
fun main(args: Array<String>) {
SpringApplication.run(App::class.java, *args)
}
@RestController
class Controller(private val resourceService: ResourceService) {
private val dispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
@GetMapping("/resource/{id}")
fun getResource(@PathVariable("id") id: String) = runBlocking(dispatcher) {
resourceService.get(id).also { logger.info { Thread.currentThread().name + "Returning $it" } }
}
}
@Service
class ResourceService(private val networkResourceRepository: NetworkResourceRepository) {
suspend fun get(id: String): Resource {
logger.info { Thread.currentThread().name + "Getting resource" }
return networkResourceRepository.get(id)
}
}
@Repository
class NetworkResourceRepository {
suspend fun get(id: String): Resource = withContext(Dispatchers.IO) {
logger.info { Thread.currentThread().name + "Getting resource from n/w" }
//IO operation
Resource("resource data")
}
}
data class Resource(val data: String)
runBlockingfrom your endpoint. Every request in SpringBoot is handled in a separate thread anyways. YourResourceServiceshould not expose suspended functions. Make coroutines inside but hide them to outsiders. - Neo