Getting started with Spring and Coroutines - Part 3
Things that don't work (Annotation magic)

Search for a command to run...
Things that don't work (Annotation magic)

No comments yet. Be the first to comment.
Recently, I wanted to rewrite a JUnit integration test for my Spring Boot application. Since it was an integration test, I used Testcontainers to start a database. While there are Kotest extensions for Spring and Testcontainers we have the problem th...

I am happy to tell you all about an upcoming Kotest feature that will be in one of the future versions (hopefully the next one ;) ) of Kotest: YAML matchers What makes those matchers special for me is that they are my first contribution to the Kotest...

1. Introduction Beginning in December, I had the chance to participate in a Bertelsmann scholarship. I was allowed to start the Udacity nanodegree "AI Programming with Python" for free. Last month, I finished the nanodegree successfully, and therefor...

I recently had to work with Kafka Streams for the first time and, as usual, when encountering a new technology, made some mistakes. I want to share the learnings from working the first time with Kafka Streams with you. Without getting too much into d...

Introduction This year was the first one in which I participated in Advent of Code (AoC). I am not sure why I haven't noticed it before. Maybe I was not interested, since coding competitions are not related to my daily work. Additionally, competition...
Even Coroutines have to be taken with a grain of salt. This article is about the things that will not work when you go reactive in Spring. Though Spring is trying to support Kotlin features as good as possible it still lacks some.
Initially, I was planning to give you some specific examples of things that do not work. Like this annotation or that one. But while doing my research I came to a different conclusion. It is not about specifics that do not work well with Coroutines. I came to a general conclusion:
Expect that anything that uses annotation magic does not work
There might be exceptions, since I am pretty sure that I do not know every single annotation, but regarding the ones I use most often, nothing works with Coroutines out of the box.
Let's take a look at some examples.
If you want to annotate a suspend function with @Cacheable you will be quite disappointed. Due to the fact that the Kotlin compiler will modify the method signature and add a Continuation parameter. Basically, the Continuation will mess with the caching interceptor. There is a pretty good SoF answer regarding this.
The general solution for all incompatibilities presented here is to go back to the basics, i.e., we code manually what the annotations do for us automagically. In the case of the @Cacheable annotation we must use the CacheManager ourselves. E.g., when using Caffeine, we need a CacheManager bean:
@Bean
fun cacheManager(caffeine: Caffeine<Any, Any>): CacheManager =
CaffeineCacheManager().apply {
setCaffeine(Caffeine.newBuilder().maximumSize(10_000))
}
And then we can use the Bean to create Caches when needed:
private val cache: Cache = cacheManager.getCache("cacheName")!!
Lastly, we check presence and insert into the cache when necessary:
return if (cache[key] != null) {
cache.get(key, MyClass::class.java)
} else {
...
cache.put(key, myClass)
}
Additionally, as you might have read in the SoF answer, you could also fall back to using a Deferred as return value. But using a Deferred here might bring other problems since we are no longer invoking a method but rather starting a job.
Currently, if you do not happen to use the R2DB driver, there is no ReactiveTransactionManager in your application autoconfigured. This means that reactive methods, like suspend functions, cannot be used with the @Transactional annotation.
As far as I know, it is problematic to stretch a transaction across several threads. And in case of suspend functions, you might switch the thread several times.
If you want to stick to suspend functions, you will have to use the TransactionTemplate. You should be able to inject an org.springframework.transaction.PlatformTransactionManager and then create a TransactionTemplate:
val transactionTemplate = TransactionTemplate(platformTransactionManager)
You can then call the executeWithoutResult or execute methods. But be careful. I did not test this. You should avoid calling other suspend functions without runBlocking. The way I see it, everything you do within one of those two execute functions should be non-reactive due to the reason mentioned above.
As you can see from this issue (effective 11.10.2022) there is no support for the @CircuitBreaker annotation. Nevertheless, Resilience4J supports Kotlin Coroutines.
As you can read in the documentation you may execute or decorate the suspend functions. Something like this should work:
private suspend fun <T> makeCall(yourLambda: suspend () -> T): T =
circuitBreaker.executeSuspendFunction {
timeLimiter.executeSuspendFunction(yourLambda)
}
If you are a fan of the declarative FeignClient I have to disappoint you, too. There is currently no support for reactive methods.
There is a community project called "Feign Reactive" which you can find here. It allows you to use Mono or Flux as return types. You could then transform those into Coroutines by using the extension methods from kotlinx-coroutines-reactor like await().
Not even the @Bean annotation works with suspend functions. Somehow, the inserted Continuation also messes with Spring's ConstructorResolver.
Okay, this is quite an artificial example since there should be no suspend functions involved when creating a bean. Still, it shows that there are some problems left to be solved.
As always with new technologies the surrounding ecosystem must catch up. Though, Kotlin and its Coroutines aren't that new, a framework as big a Spring cannot change quickly.
There was a project that tried to overcome the limitations but it hasn't received any updates in a while (https://github.com/konrad-kaminski/spring-kotlin-coroutine). So, I suppose it is EOL.
Personally, I hope that with Spring 6 and Spring Boot 3 there will be better support out-of-the-box for all the things I have shown in this article. We will know more at the end of the year.