Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 338 Vote(s) - 3.54 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to inject dependencies in a ktor Application

#1
The documentation talks about dependency injection but does not really show how it is being done.

Documentation is not completed as well and has a bunch of place holders:

[To see links please register here]


I tried to create my main function in a way that it accepts parameter (which is my dependency) but that failed on the test side when I call `withTestApplication`.
I looked into the application code and saw that Application accepts a configuration object but I have no idea how I can change that configuration object to inject some dependencies inside of it.

package org.jetbrains.ktor.application

/**
* Represents configured and running web application, capable of handling requests
*/
class Application(val environment: ApplicationEnvironment) : ApplicationCallPipeline() {
/**
* Called by host when [Application] is terminated
*/
fun dispose() {
uninstallAllFeatures()
}
}

/**
* Convenience property to access log from application
*/
val Application.log get() = environment.log


In the test code using `withTestApplication` I have something similar to the below:

@Test
internal fun myTest() = withTestApplication (Application::myMain)

The above `withTestApplication` would fail if I call `myMain` with parameters (parameters that I need to mock and inject.)

**Update:**

The issue is that in my request handling, I am using a dependency class that connects to other web services outside and does some requests, I need a way to be able to inject this so in my tests I can stub/mock it and change its behavior based on my test cases.
Reply

#2
Easy example with **Koin**

1) At first, define our prod and test dependencies:

val prodModule = module {
single<IFirstService> { RealFirstService() }
single<ISecondService> { RealSecondService() }
}

val testModule = module {
single<IFirstService> { FakeFirstService() }
single<ISecondService> { FakeSecondService() }
}

2) Then add DI initialization before app start:

fun main(args: Array<String>) {
startKoin(listOf(prodModule))
embeddedServer(Netty, commandLineEnvironment(args)).start(true)
}

3) Use inject in Application or in routes:

fun Application.apiModule() {
val firstService: IFirstService by inject()
val secondService: ISecondService by inject()
...
routing {
someApi(inject(), inject())
}
}

4) *(Optional)* For tests just add initialization in testModule before run test:

fun testApp(test: TestApplicationEngine.() -> Unit) {
withTestApplication({
... // configure your test app here

stopKoin() // Need to stop koin and restart after other tests
startKoin(listOf(testModule)) // Init with test DI

apiModule() // Run you application
})
}

// And run tests
@Test
fun `get events`() = testApp {
// do tests
}

That's all!
Reply

#3
Ktor doesn't have a built-in dependency injection mechanism. If you need to use DI, you will need to use any framework you like, such as Guice for example. It would look something like this:

fun Application.module() {
Guice.createInjector(MainModule(this))
}

// Main module, binds application and routes
class MainModule(private val application: Application) : AbstractModule() {
override fun configure() {
bind(Application::class.java).toInstance(application)
... other bindings ...
}
}

This way you delegate application composition to Guice and build it up as any other application. E.g. you might compose different parts of your application like this:

```kotlin
class Hello @Inject constructor(application: Application) {
init {
application.routing {
get("/") {
call.respondText("Hello")
}
}
}
}
```

and then bind it in a main module:

bind(Hello::class.java).asEagerSingleton()

`asEagerSingleton` is needed so that Guice will create it eagerly since no other service would query it.
Reply

#4
After experimenting a bit with Koin, Kodein, and Daggers, we ended up using **spring-context** with Ktor. It works like a charm.

**Step 1**: In our Gradle file:

implementation(group = "org.springframework", name = "spring-context", version = "5.3.5")

Change that to any `spring-context` version you prefer.

**Step 2**: We defined our `@Component`s, `@Configuration`s, `@Bean`s etc. like we would with any spring application.


**Step 3**: in our `main` method, we have a little bit of glue code to explicitly initialize the DI context and use it for initializing Ktor:

```kotlin
val ctx = AnnotationConfigApplicationContext("YOUR.ROOT.PACKAGE.GOES.HERE")
val someBean = ctx.getBean(SomeBean::class.java) // you can get any bean you need to use in your top-level glue code

// ... go ahead with KTOR configuration as usual. You can access any bean using the ctx variable.
```

Of course this glue code where we explicitly interact with the spring context is done only once. The rest of the project consist of components that reference each other using the regular spring-context way.
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through