koin icon indicating copy to clipboard operation
koin copied to clipboard

Ktor DI service per Request

Open inemtsev opened this issue 2 years ago • 1 comments

Is your feature request related to a problem? Please describe. We would like to inject a service instance for every request. So it will be different for every request, but the same everywhere within this request.

Describe the solution you'd like It would be great to have a request-scope that integrates with Ktor

Describe alternatives you've considered Currently, I am considering rolling my own solution for this. But, I know some DI frameworks for C# for example have this type of scope.

inemtsev avatar Jun 29 '22 09:06 inemtsev

I ended up using the Scoped feature and attached that scope at the beginning of the pipeline.. It works well, but adding this functionality to koin-ktor would be awesome

inemtsev avatar Jul 06 '22 07:07 inemtsev

I ended up using the Scoped feature and attached that scope at the beginning of the pipeline.. It works well, but adding this functionality to koin-ktor would be awesome

Could you explain it with some details, please ? (for beginners) .. or maybe you have a link to some repository Thank you.

yuri-sinyakov avatar Mar 01 '23 12:03 yuri-sinyakov

@SinyakovYuri you will need middleware, that creates scope and closes it after request is sent to user.

We are extending KoinScopeComponent for attaching resources to it and close them (but I think generic scope will do):

/**
 * Koin scope, that has attached resources.
 *
 * Contains list of [Closeable], which should be released upon [closeScope] call.
 */
class ScopeWithResources : KoinScopeComponent {
    override val scope: Scope by lazy { createScope(this) }

    private val resources = mutableListOf<Closeable>()

    /**
     * Attach [resource] to scope.
     */
    fun <T : Closeable> with(resource: T): T {
        resources.add(resource)
        return resource
    }

    /**
     * Release resources and close scope.
     */
    override fun closeScope() {
        resources.forEach { it.close() }
        super.closeScope()
    }
}

Then you need actual middleware, that creates this scope and attaches it to attributes:

private val requestScopeKey = AttributeKey<Scope>("requestScopeKey")

// Helper extension to get scope in ApplicationRequest 
val ApplicationRequest.scope
    get() = this.call.attributes[requestScopeKey]

val RequestScope = createApplicationPlugin(name = "RequestScope") {
    on(CallSetup) { call ->
        val scopeComponent = ScopeWithResources()
        call.attributes.put(requestScopeKey, scopeComponent.scope)
    }

    on(ResponseSent) {
        call.attributes[requestScopeKey]?.closeScope()
    }
}

And you are ready to go, just install(RequestScope) in application and get scope from extension function to create scoped instances, that will be released on request end.

install(RequestScope)

routing {
    get("/") {
        call.request.scope.get<MyService>()
    }
}

Upd. fixed https://github.com/InsertKoinIO/koin/issues/1374#issuecomment-1701823968

floatdrop avatar Mar 01 '23 12:03 floatdrop

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 29 '23 13:07 stale[bot]

still on the roadmap 👍

arnaudgiuliani avatar Aug 02 '23 12:08 arnaudgiuliani

The solution by @floatdrop is broken if multiple requests are active, you can't save a reference to the scope as a local variable in the plugin instance.

this is the correct approach:

class CallScope : KoinScopeComponent {
    override val scope = createScope()
}

private val callScopeKey = AttributeKey<Scope>("callScopeKey")

val ApplicationCall.scope
    get() = this.attributes[callScopeKey]

val CallScopePlugin = createApplicationPlugin("CallScopePlugin") {

    on(CallSetup) { call ->
        val callScope = CallScope()
        call.attributes.put(callScopeKey, callScope.scope)
        // Optional, you can inject the call itself in the container so it's available to the scoped beans
        callScope.scope.declare(call) 
    }

    on(ResponseSent) { call ->
        call.attributes[callScopeKey].close()
    }
}

pabloogc avatar Aug 31 '23 21:08 pabloogc

Added in 3.5.0, part of the main Koin plugin 👍 2e491fb0977be9bcaec2ec95be90751d3ae9456a

arnaudgiuliani avatar Sep 07 '23 13:09 arnaudgiuliani

@arnaudgiuliani the request headers are not available to scoped beans out of the box. is there any intention to make this available in the future.

sharifahmad2061 avatar Jan 04 '24 13:01 sharifahmad2061

@sharifahmad2061 you mean having access to the request object from Koin scope?

arnaudgiuliani avatar Jan 25 '24 09:01 arnaudgiuliani

@arnaudgiuliani Are there any examples for RequestScope usage? Does RequestScope allow to get the applicationCall or any of the request details, so they can be passed to the any of scoped instances constructors?

perracodex avatar Feb 09 '24 20:02 perracodex

take a look at https://insert-koin.io/docs/reference/koin-ktor/ktor#resolve-from-ktor-request-scope-since-350 it shoud help you understand details of RequestScope

arnaudgiuliani avatar Feb 15 '24 15:02 arnaudgiuliani