Just a basic DI question. I've always learned that...
# ktor
a
Just a basic DI question. I've always learned that property injection is not ideal, and one should always prefer constructor injection since it makes testability better among others things. However, in almost all Ktor examples for DI I see the equivalent of property injection but functionally. What do I mean?
Copy code
fun Application.myModule() {
    val myDep: Dep by dependencies // or val myDep: Dep = dependencies.resolve();

    // more code
}
I don't see why we should make make the functions so aware of a DI container. Also, this isn't really dependency injection. This is just a service container then 🙂 This is also pretty prevalent in the official KotlinConf backend app which runs on Koin. Since modules are functions, and functions have arguments, isn't it better to do:
Copy code
fun Application.myModule(myDep: Dep) { ... }

// And within Application scope
myModule(myDep = dependencies.resolve())
Is there a reason to do one over the other?
v
I always prefer property DI, among other things because that increases testability, besides that it imho is more maintaineable and more readable.
a
Wait, how am I misunderstanding it? How does it increase testability compared to constructor /function-argument injection? If
Application.myModule
gets a new dependency, everything still compiles when I do this:
Copy code
fun Application.myModule() {
    val myDep: Dep by dependencies // or val myDep: Dep = dependencies.resolve();
    val anotherDep: Foo by dependencies

    // more code
}
But my tests can fail, because I didn't provide the
Foo
dependency in some implicit manner before setting up the tests.
b
It's a matter of preference, though I tend to prefer using module parameters. The only issue there is that we don't have a good way to reference them programatically, so the common approach of having a single top-level application module calling down into the rest doesn't stack up, so you need to rely on including items in the properties
ktor.application.modules
and
ktor.application.dependencies
We just published a blogpost yesterday that goes into the different methods https://blog.jetbrains.com/kotlin/2025/07/modular-ktor-building-backends-for-scale/
a
Great, I was reading that. "This approach can work well for applications of any size, with the sole caveat that our modules are now strongly coupled at compile time, so they’re no longer interchangeable at runtime." How would one even interchange a module at runtime? What is the use-case there?
v
Wait, how am I misunderstanding it? How does it increase testability compared to constructor /function-argument injection?
Might be different with those functions, I was talking about constructor vs. property injection. If you inject in the constructor, the constructor changes with each added injectable so you need to adjust all callers of the constructor to also provide that injectable even if it isn't needed for the test. Even if you have additionally a no-arg non-private constructor because it is a proxyable CDI bean that would not help as typically with constructor-injection you then also make the properties private and final so cannot manipulate them in the test. If you have property injection, the test can simply set those properties that are actually needed for the test in question.
b
How would one even interchange a module at runtime? What is the use-case there?
The use-case here is for when you want to load different modules based on your configuration files, like enabling some features based on the environment, or if you want to use a base server impl that can be extended.
a
@Vampire I understand. Although the issues you mention seem to me related to SRP violations. On the other hand, seeing how active you are in Gradle development which is a large codebase, I am more than willing to take your word for it 🙂
@Bruce Hamilton Ah that's what I thought. IMHO that is not really runtime interchangeability, since you are not changing the dependencies when it was running. But you are defining the dependencies during startup. But it is semantics. It isn't compile time, and the opposite of compile time is indeed runtime. I thought with runtime interchangeability it was meant that while the Ktor was being active dependencies where changed about. And I had a hard time seeing a use-case for it, especially in the day and age of zero-downtime-deployments etc
b
Oh, I can see the confusion there. You could theoretically replace modules as the server is running, but generally I just mean on startup.
a
Thanks for the great answers. And great blog post.
🙇 1