https://kotlinlang.org logo
#spring
Title
# spring
a

Asif Ahmed

03/08/2024, 12:56 PM
Hello community, I’m currently working on a Spring Boot web application with a traditional three-layer architecture: controller, service, and
ApiClient
for blocking calls. Due to certain limitations, adopting a fully reactive approach isn’t feasible at the moment. However, we are keen on finding a middle ground to improve performance through parallel calls. To achieve this, we are exploring Kotlin Coroutines(We have also explored CompletableFuture and @Asyc but not happy with it ). As a relative newcomer to coroutines, I’ve implemented a solution that seems to work, but I’m uncertain if it’s the optimal approach. Below is a simplified snippet of the current implementation:
Copy code
@Service
class Service(
    private val clientA: ClientA,
    private val clientB: ClientB,
    private val clientC: ClientC
) {

    fun someFunction(): OrderForm {
        runBlocking(RequestCoroutineContext()) {
            val result1 = async(Dispatchers.IO) { callA() }
            val result2 = async(Dispatchers.IO) { callB() }

            val result3 = async(Dispatchers.IO) { callC(result1.await()) }

            OrderForm(result1.await(), result2.await(), result3.await())
        }
    }

    private suspend fun callA() = clientA.get()

    private suspend fun callB() = clientB.get()

    private suspend fun callC(value: SomeType) = clientC.get(value)
}
Initially, the
ApiClient
methods were not marked as
suspend
, but I’m now considering moving towards making these calls suspendable to bring them closer to the client:
Copy code
class ClientA {
    suspend fun getA() {
        withContext(Dispatchers.IO) {
            // blocking API call
        }
    }
}

class ClientB {
    suspend fun getB() {
        withContext(Dispatchers.IO) {
            // blocking API call
        }
    }
}

class ClientC {
    suspend fun getC(value: SomeType) {
        withContext(Dispatchers.IO) {
            // blocking API call
        }
    }
}
However, attempting to call these suspendable functions in
someFunction()
resulted in an exception
Copy code
Handler dispatch failed: java.lang.NoClassDefFoundError: kotlinx/coroutines/reactor/MonoKt","stack_trace":"jakarta.servlet.ServletException: Handler dispatch failed: java.lang.NoClassDefFoundError: kotlinx/coroutines/reactor/MonoKt
related to
NoClassDefFoundError: kotlinx/coroutines/reactor/MonoKt
. I suspect this may be due to the fact that the actual API calls are not reactive and suspendable. I’m seeking recommendations on the best way to handle coroutines in a single thread per request model with Spring Boot. I want to avoid marking private functions as suspended and continually wrapping code in scopes. Any insights or guidance would be greatly appreciated.
r

Robert Jaros

03/08/2024, 1:12 PM
Adding
org.jetbrains.kotlinx:kotlinx-coroutines-reactor
dependency should help with these errors.
a

Asif Ahmed

03/08/2024, 1:19 PM
Thanks @Robert Jaros for the reply. Yeah, I saw that somewhere, and they were mentioning that adding this was resulting in some undesired behaviour, and I just wanted to understand the reason behind it. and also do you have recommendations please?
r

Robert Jaros

03/08/2024, 1:25 PM
Honestly, it's a bit weird having blocking controller running coroutines with
runBlocking
just to call blocking API with
<http://Dispatchers.IO|Dispatchers.IO>
. I'm not a Spring expert, but how could this improve performance? 🙂
a

Asif Ahmed

03/08/2024, 1:38 PM
It’s just offloading blocking API calls to separate lighter threads (at least that was my understanding), which allows you to run in parallel, I guess. I have run the performance test and see a little improvement as it was applied to part of the code. Having said that, I’m no expert, and I understand it’s not a recommended approach, but out of all the options explored, this seems to be easy to integrate with and doesn’t bloat like CompletableFutures, and we can’t go completely reactive for now 😞 and if i can ask you one more question using
async(<http://Dispatchers.IO|Dispatchers.IO>) { callA() }
in context with
withContext(<http://Dispatchers.IO|Dispatchers.IO>)
will use the spawned thread from async and doesn’t use its own pool right
d

David Kubecka

03/08/2024, 1:46 PM
BTW: You should be able to declare the IO dispatcher for the whole
runBlocking
scope:
Copy code
runBlocking(RequestCoroutineContext() + <http://Dispatchers.IO|Dispatchers.IO>) {
  val result1 = async { callA() }
gratitude thank you 1
If you are only after making the clinet calls in paralellel (and not actually achievning non-blocking calls) there's even no point in using suspend wrappers anywhere, since the underlying function is blocking anyway.
👍 1
k

Klitos Kyriacou

03/08/2024, 1:56 PM
What do you mean by "lighter threads"? Async calls launched with
<http://Dispatchers.IO|Dispatchers.IO>
use Java threads in the same way as
CompletableFuture
does.
a

Asif Ahmed

03/08/2024, 2:07 PM
@Klitos Kyriacou Yes you are right , it was wrong terminology to use in this context
j

Johan

03/08/2024, 3:09 PM
I recommend to take a look at

https://www.youtube.com/watch?v=szl3eWA0VRw

gratitude thank you 1
j

Jacob

03/08/2024, 6:51 PM
Using async for result3 is silly since you immediately await the result
The only thing it makes sense to async in the example is result2
a

Asif Ahmed

03/11/2024, 5:50 AM
@Jacob agreed to your point, but in actual code, that was not the case.
6 Views