Skip to content

Commit

Permalink
Fix Ktor Koin plugin with the right start process, and open get() to …
Browse files Browse the repository at this point in the history
…external Global context fallback
  • Loading branch information
arnaudgiuliani committed Sep 15, 2023
1 parent b94245b commit 3bff0a7
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 20 deletions.
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 @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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.ext

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.Test
import org.koin.core.context.startKoin
import org.koin.core.logger.Level
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class KoinPluginRunTest {

@Test
fun `minimalistic app run`() {
testMyApplication {
val response = it.get("testurl") {}

assertEquals(HttpStatusCode.OK, response.status)
assertTrue { response.bodyAsText().contains("Test response") }
}
}

@Test
fun `run outside context`() {
startKoin {
printLogger(Level.DEBUG)
modules(
module {
single<String> { "Reproduction test" }
}
)
}

embeddedServer(
Netty,
module = {
val test by inject<String>()
println(test)
},
).start()
}
}

private fun testMyApplication(test: suspend (jsonClient: HttpClient) -> Unit) = testApplication {
application {
install(Koin) {
modules(
module {
single { this@application }
single(createdAtStart = true) { KtorMyModule(get()) }
},
)
}
}
test.invoke(createClient {})
}

private fun testMyApplicationNoKoin(test: suspend (jsonClient: HttpClient) -> Unit) = testApplication {
application {

}
test.invoke(createClient {})
}

class KtorMyModule(application: Application) {
init {
application.routing {
get("testurl") { call.respond(HttpStatusCode.OK, "Test response") }
}
}
}

0 comments on commit 3bff0a7

Please sign in to comment.