I would like to understand what are the best practices on how to use kotlin coroutines in a Spring server application, specifically about the CoroutineContext.
Let's say I have a Controller like this:
@Controller
class ProductController {
@Autowired
lateinit var productRepository: ProductRepository
@Autowired
lateinit var mapper: ProductDtoMapper
@GetMapping("/")
suspend fun getProduct(): ProductDTO {
val product = productRepository.getFirst()
val productDTO = mapper.mapToDto(product)
return productDto
}
}
Then my Repository is like:
@Repository
class ProductRepository(private val productService: ProductService) {
suspend fun getFirst(): Product = withContext(Dispatchers.IO) {
return productService.get()
}
}
I noticed that the code inside the getProduct function is executed in a CoroutineContext that is Unconfined, which means that in the example above, after productRepository.getFirst() returns, the execution resumes in the same thread defined in the ProductRepository.getFirst method.
Now, in this specific example probably this doesn't matter but I was thinking that with the application growing, this might cause some problems.
For example, let's say that the ProductService is doing some operations that is blocking the thread: in this case I could decide to create a fixed number of threads to limit the amount of threads to handle this kind of operation. The code might look to something like this:
@Repository
class ProductRepository(private val productService: ProductService) {
private val dispatcher = Executors.newFixedThreadPool(10).asCoroutineDispatcher()
suspend fun getFirst(): Product = withContext(dispatcher) {
return productService.get()
}
}
So, as stated above, after productRepository.getFirst() returns, the execution resumes in the same thread defined in the ProductRepository.getFirst method.
That means that also the mapping in ProductController will be executed in the same thread, and if this is also a blocking operation the thread will continue to execute operations that was not suppose to execute.
I come from Android and this kind of behavior is not recommended, since it might end up executing code that is doing cpu intensive operations in the main thread and this will be blocking the ui.
So, back to the server application, I would define the CoroutineContext that I want to use in the ProductController so I will be sure that the code after productRepository.getFirst() will be executed in the same context that I decided to be. It would look to something like this:
@Controller
class ProductController {
// other code
@GetMapping("/")
suspend fun getProduct(): ProductDTO = withContext(Dispatchers.Default) {
val product = productRepository.getFirst()
val productDTO = mapper.mapToDto(product)
return productDto
}
}
In this way I found way more easier mentally track the context in which the code is executed.
So my questions are:
- Is it fine to use this
Unconfinedcontext? - Might this behavior be a problem when need to scale the application?
- What is a good practice for this case?
If I wrote something wrong or my question is not clear let me know.