Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Ktor Koin plugin with the right start process #1657

Merged
merged 3 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/koin-core/src/commonMain/kotlin/org/koin/core/Koin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,6 @@ class Koin {
val duration = measureDuration {
instanceRegistry.createAllEagerInstances()
}
logger.debug("Koin created eager instances in $duration ms")
logger.debug("Created eager instances in $duration ms")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class KoinApplication private constructor() {
if (koin.logger.isAt(Level.INFO)) {
val duration = measureDuration { loadModules(modules) }
val count = koin.instanceRegistry.size()
koin.logger.display(Level.INFO, "Koin started with $count definitions in $duration ms")
koin.logger.display(Level.INFO, "Started $count definitions in $duration ms")
} else {
loadModules(modules)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.test.TestResult
import kotlinx.coroutines.test.runTest
import org.koin.Simple
import org.koin.core.annotation.KoinInternalApi
Expand Down Expand Up @@ -355,26 +356,30 @@ class ParametersInjectionTest {

@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun `inject across multiple threads`() = runTest {
val app = koinApplication {
modules(
module {
factory { (i: Int) -> Simple.MyIntFactory(i) }
},
)
}
fun `inject across multiple threads`(): TestResult {
val times = 100

return runTest {
val app = koinApplication {
modules(
module {
factory { (i: Int) -> Simple.MyIntFactory(i) }
},
)
}

val koin = app.koin
val koin = app.koin

repeat(1000) {
val range = (0 until 1000)
val deferreds = range.map {
async(Dispatchers.Default) {
koin.get<Simple.MyIntFactory> { parametersOf(it) }
repeat(times) {
val range = (0 until times)
val deferreds = range.map {
async(Dispatchers.Default) {
koin.get<Simple.MyIntFactory> { parametersOf(it) }
}
}
val values = awaitAll(*deferreds.toTypedArray())
assertEquals(range.map { it }, values.map { it.id })
}
val values = awaitAll(*deferreds.toTypedArray())
assertEquals(range.map { it }, values.map { it.id })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fun main(args: Array<String>) {
fun Application.mainModule() {
install(CallLogging)
install(Koin) {
slf4jLogger(Level.DEBUG)
printLogger(Level.DEBUG)
modules(appModule)
}

Expand All @@ -42,6 +42,7 @@ fun Application.mainModule() {
get("/hello") {
val newId = call.scope.get<ScopeComponent>().id
println("ScopeComponent.id = $newId")
assert(Counter.init == 1)
call.respondText(helloService.sayHello())
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.koin.sample

import org.koin.sample.Counter.init
import java.util.UUID

class HelloRepository {
Expand All @@ -10,7 +11,15 @@ interface HelloService {
fun sayHello(): String
}

object Counter {
var init = 0
}

class HelloServiceImpl(val helloRepository: HelloRepository) : HelloService {
init {
println("created at start")
init++
}
override fun sayHello() = "Hello ${helloRepository.getHello()}!"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.testing.*
import org.junit.Before
import org.junit.Test
import org.koin.test.AutoCloseKoinTest
import kotlin.test.assertEquals
Expand All @@ -13,6 +14,11 @@ import kotlin.test.assertTrue

class ApplicationJobRoutesTest : AutoCloseKoinTest() {

@Before
fun before(){
Counter.init = 0
}

@Test
fun testHelloRequest() = testApplication {
val response = client.get("/hello")
Expand Down
3 changes: 2 additions & 1 deletion ktor/koin-ktor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ repositories {
}

dependencies {
// Koin
api "io.insert-koin:koin-core:$koin_version"
testImplementation "io.insert-koin:koin-test-junit4:$koin_version"

// Ktor
api "io.ktor:ktor-server-core:$ktor_version"
testImplementation "io.ktor:ktor-server-test-host:$ktor_version"
testImplementation "io.ktor:ktor-server-netty:$ktor_version"
}

// Ensure "org.gradle.jvm.version" is set to "8" in Gradle metadata.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import io.ktor.server.application.*
import org.koin.core.Koin
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.core.scope.Scope
import org.koin.ktor.plugin.KOIN_ATTRIBUTE_KEY
import org.koin.ktor.plugin.KOIN_KEY
import org.koin.ktor.plugin.KOIN_SCOPE_ATTRIBUTE_KEY

/**
* Ktor Koin extensions for ApplicationCall class
Expand Down Expand Up @@ -71,4 +73,4 @@ fun ApplicationCall.getProperty(key: String, defaultValue: String) =
/**
* Help work on ModuleDefinition
*/
fun ApplicationCall.getKoin(): Koin = application.attributes.get(KOIN_ATTRIBUTE_KEY).koin
fun ApplicationCall.getKoin(): Koin = application.getKoin()
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ package org.koin.ktor.ext

import io.ktor.server.application.*
import org.koin.core.Koin
import org.koin.core.context.GlobalContext
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.KoinAppDeclaration
import org.koin.ktor.plugin.KOIN_ATTRIBUTE_KEY
import org.koin.ktor.plugin.Koin
import org.koin.ktor.plugin.setKoinApplication

/**
* Ktor Koin extensions
Expand All @@ -28,10 +32,17 @@ import org.koin.ktor.plugin.KOIN_ATTRIBUTE_KEY
* @author Laurent Baresse
*/



/**
* Help work on ModuleDefinition
*/
fun Application.getKoin(): Koin = attributes.get(KOIN_ATTRIBUTE_KEY).koin
fun Application.getKoin(): Koin =
attributes.getOrNull(KOIN_ATTRIBUTE_KEY)?.koin ?: run {
val defaultInstance = GlobalContext.getKoinApplicationOrNull() ?: error("No Koin instance started. Use install(Koin) or startKoin()")
setKoinApplication(defaultInstance)
attributes[KOIN_ATTRIBUTE_KEY].koin
}

/**
* inject lazily given dependency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ fun Route.getProperty(key: String, defaultValue: String) =
/**
* Help work on ModuleDefinition
*/
fun Route.getKoin(): Koin = application.attributes.get(KOIN_ATTRIBUTE_KEY).koin
fun Route.getKoin(): Koin = application.getKoin()
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ inline fun <reified T> Routing.getProperty(key: String, defaultValue: T) =
/**
* Help work on ModuleDefinition
*/
fun Routing.getKoin(): Koin = application.attributes.get(KOIN_ATTRIBUTE_KEY).koin
fun Routing.getKoin(): Koin = application.getKoin()
42 changes: 30 additions & 12 deletions ktor/koin-ktor/src/main/kotlin/org/koin/ktor/plugin/KoinPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import io.ktor.util.*
import org.koin.core.KoinApplication
import org.koin.core.scope.Scope
import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.koinApplication

/**
* @author Arnaud Giuliani
Expand All @@ -32,21 +31,34 @@ import org.koin.dsl.koinApplication
* Ktor Feature class. Allows Koin Context to start using Ktor default install(<feature>) method.
*
*/
val Koin = createApplicationPlugin(name = "Koin", createConfiguration = { KoinApplication.init() }) {
val koinApplication = setupKoinApplication()
setupMonitoring(koinApplication)
setupKoinScope(koinApplication)
}

// Plugin
val Koin = createApplicationPlugin(name = "Koin", createConfiguration = ::koinApplication) {
private fun PluginBuilder<KoinApplication>.setupKoinApplication(): KoinApplication {
val koinApplication = pluginConfig
application.attributes.put(KOIN_ATTRIBUTE_KEY, koinApplication)
koinApplication.createEagerInstances()
application.setKoinApplication(koinApplication)
return koinApplication
}

fun Application.setKoinApplication(koinApplication: KoinApplication){
attributes.put(KOIN_ATTRIBUTE_KEY, koinApplication)
}

private fun PluginBuilder<KoinApplication>.setupMonitoring(koinApplication: KoinApplication) {
val monitor = environment?.monitor
monitor?.raise(KoinApplicationStarted, koinApplication)
// Core Plugin
monitor?.subscribe(ApplicationStopping) {
monitor.raise(KoinApplicationStopPreparing, koinApplication)
koinApplication.koin.close()
monitor.raise(KoinApplicationStopped, koinApplication)
}
}

private fun PluginBuilder<KoinApplication>.setupKoinScope(koinApplication: KoinApplication) {
// Scope Handling
on(CallSetup) { call ->
val scopeComponent = RequestScope(koinApplication.koin)
Expand All @@ -57,14 +69,20 @@ val Koin = createApplicationPlugin(name = "Koin", createConfiguration = ::koinAp
}
}

fun Application.koin(configuration: KoinAppDeclaration) = pluginOrNull(Koin)?.let {
attributes.getOrNull(KOIN_ATTRIBUTE_KEY)?.apply(configuration)
} ?: install(Koin, configuration)

const val KOIN_KEY = "KOIN"
val KOIN_ATTRIBUTE_KEY = AttributeKey<KoinApplication>(KOIN_KEY)

val ApplicationCall.scope: Scope get() = this.attributes[KOIN_SCOPE_ATTRIBUTE_KEY]

const val KOIN_SCOPE_KEY = "KOIN_SCOPE"
val KOIN_SCOPE_ATTRIBUTE_KEY = AttributeKey<Scope>(KOIN_SCOPE_KEY)
val KOIN_SCOPE_ATTRIBUTE_KEY = AttributeKey<Scope>(KOIN_SCOPE_KEY)

//TODO move both to ext file
/**
* Scope property to let your resolve dependencies from Request Scope
*/
val ApplicationCall.scope: Scope get() = this.attributes.getOrNull(KOIN_SCOPE_ATTRIBUTE_KEY) ?: error("Koin Request Scope is not ready")
/**
* Run extra koin configuration, like modules()
*/
fun Application.koin(configuration: KoinAppDeclaration) = pluginOrNull(Koin)?.let {
attributes.getOrNull(KOIN_ATTRIBUTE_KEY)?.apply(configuration)
} ?: install(Koin, configuration)
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
/*
* Copyright 2017-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.koin.ktor.plugin

import org.koin.core.Koin
import org.koin.core.component.KoinScopeComponent
import org.koin.core.component.createScope

/**
* Request Scope Holder
*
* @author Arnaud Giuliani
*/
class RequestScope(private val _koin: Koin) : KoinScopeComponent {
override fun getKoin(): Koin = _koin
override val scope = createScope()
Expand Down
Loading
Loading