Hey everyone, not sure if this is the right place ...
# ktor
g
Hey everyone, not sure if this is the right place to ask (my apologies if this is not the right place) - has anyone tried using Koin with Ktor, and creating a scope that lasts for the duration of a request? For example, if I wanted to be able to inject something like a trace id, how would I go about doing that? Thanks!
e
Not sure about Koin but I use this for traceId: https://ktor.io/servers/features/call-id.html
g
Yep, I'm aware of CallId, but I would like to attach more than just a call id, and be able to use it from areas of code that are not dependent on Ktor classes/functions
e
Could you do something like
Copy code
import io.ktor.application.ApplicationCall
import io.ktor.application.ApplicationCallPipeline
import io.ktor.application.ApplicationFeature
import io.ktor.application.call
import io.ktor.util.AttributeKey
import io.ktor.util.pipeline.PipelineContext

class DiInstance

val DIAttributeKey = AttributeKey<DiInstance>("dependency-injection")

class DIFeature(val configuration: Configuration) {
    class Configuration {
        var di: DiInstance? = null
    }

    // Body of the feature
    private fun intercept(context: PipelineContext<Unit, ApplicationCall>) {
        // Make the di instance avaliable on the call
        configuration.di?.let {
            context.call.attributes.put(DIAttributeKey, it)
        }
    }

    companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, DIFeature> {
        override val key = AttributeKey<DIFeature>("DI Feature")
        override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): DIFeature {

            // Call user code to configure a feature
            val configuration = Configuration().apply(configure)
            if (configuration.di == null) {
                throw  IllegalStateException("Must supply instance of DI")
            }
            // Create a feature instance
            val feature = DIFeature(configuration)

            // Install an interceptor that will be run on each call and call feature instance
            pipeline.intercept(ApplicationCallPipeline.Call) {
                feature.intercept(this)
            }

            // Return a feature instance so that client code can use it
            return feature
        }
    }
}

/**
 * install(DIFeature){
 *     di = DiInstance()
 * }
 */
Then you can:
Copy code
val di: DiInstance = call.attributes[DIAttributeKey]
in your request handlers
g
Thank you for sharing this! I did try out something similar. Problem is, if I have a class which executes database queries, that does not have access to
call
, I wouldn't be able to inject request specific stuff in there. If I had access to
call
, I wouldn't need the DI at all - I'd use
call.attributes
directly as you've done above.
j
“be able to use it from areas of code that are not dependent on Ktor classes/functions” this sounds like you can extract CallId from route definition and send it through your architecture as any other parameter. How is your code structured? do you have any architecture?