Hi Kotlin friends, I am trying to learn how annota...
# getting-started
j
Hi Kotlin friends, I am trying to learn how annotation, reflection and continuation works. I appreciate for any comments/directions. Here is my context: I am using spring boot webflux with kotlin coroutine and openapi codegen. I want to be able pass in the request context to my application logic from the webfilter layer all the way to the business logic layer. the challenge i am facing is that: 1. webflux is running on reactor, that means the webfilter layer (interceptor) is running on reactor 2. my controllers are running in coroutine and these points to my business layer 3. my controller interfaces are generated by openapi codegen which does not provide me with flexibility of adding additional parameters (such as the
webServerExchange
object). My initial attempt is: 1. pass the context from reactor to coroutine using context capture, and later i can retrieve the context without needing the
webServerExchange
object 2. define an customized annotation such as
WithReactiveContext
3. add this annotation to my controller class so that all my suspending function will be wrapped inside an different coroutine context (instead of the default one) My questions are: 1. do you guys think this is a legit approach given my context 2. if this approach sounds okay, how do I trigger the annotation method using reflection so that all the suspending functions in annotated class is wrapped within a different coroutine context 3. if this approach does not make sense whats an alternative solution
s
Have you tried just running code with Spring Boot Webflux and
suspend
controller functions? Spring Boot Webflux and even latest releases of Spring Boot MVC have implementations of integration with Kotlin Coroutines utilizing this library underneath: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/ When
suspend fun
from REST controller is entered, Reactor's context is already injected into current coroutine context. Also, take a note that if you create project template on
<http://start.spring.io|start.spring.io>
having
kotlin
as a language and
Spring Reactive Web
as a dependency, it will automatically include
org.jetbrains.kotlinx:kotlinx-coroutines-reactor
in dependencies as well
j
I think i understand what you mean and please correct me if I am wrong, basically you could do this. simply implement the controller with
asCoroutineContext
or
withContext
. for example:
Copy code
class MyController(){
   suspend fun controller1(){ 
       val context = coroutineContext // context will be passed from reactor to this scope, and you can get the context simply from import `kotlin.coroutines.coroutineContext` or `Mono.deferContextual`
       return withContext(context) {
          ....
       }
   }
}
But this approach requires me to change all the controllers in my code base, and i am trying to avoid that. What i am trying to find out is if its possible in kotlin to do the following: declare an annotation`@ExecuteWithContext` such that the execution of the suspend function will be wrapped inside another coroutine context
Copy code
@ExecuteWithContext
class MyController(){
   suspend fun controller1(){ 
       return withContext(context) {
          ....
       }
   }
}
not sure if AOP style is the correct approach to achieve what i want
s
getting
coroutineContext
and passing it to
withContext
is redundant since
coroutineContext
is already current context, so
withContext
can be omitted entirely. So your example:
Copy code
suspend fun controller1(){ 
   val context = coroutineContext
   return withContext(context) {
          [processing]
   }
}
could be just simplified to
Copy code
suspend fun controller1(){ 
   [processing]
}
So I understand you correctly, and what you're trying to achieve is to have
suspend
controller functions with
ReactorContext
integration (same as if they were Java controller's functions returning Mono or Flux), then you should be able to just declare those functions as suspend without manually handling integration as it is being taken care of before your functions are invoked so that they are invoked already within the proper context.
You may want to enter debug mode and see who context is created and propagated. Here's how coroutineContext looks within webflux controller's suspend function. You may also look at for example https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java and https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/m[…]ramework/web/reactive/result/method/InvocableHandlerMethod.javain case you're curious how Reactor <-> Coroutines integration is handled by Spring Webflux for REST endpoints
👍 1
j
Thanks for your feedback! However i am still having trouble to get it working I already verified the context passing was working, but I am having a hard time pass
Element
as context. For example, if i want to pass MDCContext as context from the kotlin library in the webfilter i will have to add something like this:
Copy code
.contextWrite { ctx -> ctx.put(MDCContext.Key, MDCContext(mapOf("1" to "2")))}
but this will only pass the context as a
key value
pair instead of a coroutine element. So in this example I printed the context as
Copy code
[Context4{ kotlinx.coroutines.slf4j.MDCContext$Key@79272fd3=kotlinx.coroutines.slf4j.MDCContext@4424764e}, kotlinx.coroutines.UndispatchedMarker@3238f827]
but what i really need is
Copy code
[Context4{}, kotlinx.coroutines.slf4j.MDCContext@1d486104, kotlinx.coroutines.UndispatchedMarker@3238f827]
i am not sure if this is possible without modifying the source code, even in the official documentation its passed in as key value. And this looks like an open issue
i was able to resolve this with spring aop, let me know if you are interested
❤️ 1
s
I would for sure like to see how you've resolved it!
j
so to recap, my need is to enable passing context from reactor to coroutine without touch lots of code step 1: generate the context and put in reactor context holder (this step you already know, here i use
"YOUR-CONTEXT"
as the key) step 2: pass context to coroutine (this comes for free as you mentioned) step 3: define annotation along with spring or java aop and do something like this
Copy code
@Around(
        """
        @within(YourAnnotation) &&
        args(.., kotlin.coroutines.Continuation)
        """)
    fun executeFunction(joinPoint: ProceedingJoinPoint): Any? {
        return runCoroutine(joinPoint.args.last() as Continuation<Any?>) {
            withContext(coroutineContext.get(ReactorContext.Key)?.context?.get("YOUR-CONTEXT") as YourContext) {
                suspendCoroutineUninterceptedOrReturn<Any?> {
                    joinPoint.proceed(
                        joinPoint.args.sliceArray(0 until joinPoint.args.size - 1) + it)
                }
            }
        }
    }
note:
YourContext
has to be a coroutine context element for example MDCContext from kotlinx if you want to do method level you can do
@annotation(YourAnnotation)
step 4: annotate your class with
@YourAnnotation
this way you dont need to change any code, all the methods inside the class will execute with the jointPoint function
157 Views