The idea is to be able to define DI components as functions. This would unlock more advanced use cases, an obvious one being easier definition of simple web controllers and Ktor modules.
Usage
Here's an example of what this could look like.
Tegral Web Controllers
For example:
class GreeterService {
fun greet(name: String): String {
return "Hello, $name!"
}
}
@TegralDiFundef
fun Application.openApiModule() {
describe {
title = "My greeter API"
}
}
@TegralDiFundef
fun Routing.controller(gs: GreeterService) {
get("/greet/{name}") {
call.respondText(gs.greet(call.parameters["name"]!!))
}
}
fun main() {
tegral {
install(OpenApiFeature)
put(::openApiModule)
put(::controller)
}
}
Tegral DI
class GreeterService {
fun greet(name: String): String {
return "Hello, $name!"
}
}
@TegralDiFundef
fun greetWorld(gs: GreeterService) {
return gs.greet("World")
}
fun main() {
val env = tegralDi {
put(::GreterService)
put(::greetWorld)
}
val fundef = env.getOrNull<Fundef>(ofFunction(::greetWorld))
fundef.function // == ::greetWorld
fundef() // == greetWorld(...)
}
Pros, cons and remarks
Pros
Indentiation
Saves an indentation space for controllers and modules and significantly simplifies their definition.
class HelloController : KtorController() {
override fun Routing.install() {
get("class") {
call.respondText("Hello World!")
}
}
}
@TegralDiFundef
fun Routing.helloController() {
get("fun") {
call.respondText("H:ello World!")
}
}
fun main() {
tegral {
put(::GreterService)
put(::HelloController)
put(::helloController)
}
}
Ktor modules
- Is similar to what the Ktor docs recommend with Ktor modules. This could also make the transition from module-based Ktor to Tegral easier
Misc.
- Optional injection (
scope.optional()
) can easily be supported by using default parameters! (probably with nullable support, so A? = null
in a parameter would be the same as env.getOrNull<A>()
)
Cons
Qualifiers
- Retrieving qualified components becomes a huge mess
- Either provide qualifier info via annotations. Not super feasible.
- Maybe a special DSL for it?
class GreeterService {
fun greet(name: String): String {
return "Hello, $name!"
}
}
@TegralDiFundef
fun greetWorld(gs: GreeterService) {
}
fun main() {
val env = tegralDi {
put(::GreterService, named("Hellofy"))
put(
(::greetWorld).toFundef {
qualifyParameter("gs", named("Hellofy"))
}
)
}
}
- Collides with the current
put
syntax, becomes constructors are considered functions when using reflection. Either
- Find a way to differentiate between functions and constructors: this is possible using
<KFunction object>.javaConstructor != null
.
- Force the use of an annotation permanently for functions (there would still be annotations at first while this is an experiment)
Remarks
- In order to respect the "principal" of safe injections, functional definitions would have to retrieve all of the required objects within an
init
block.
Todo list
- [ ] Initial release
- [ ] Implement basic support in Tegral DI
- [ ] With experimental annotation
- [ ] With a special function
putFundef
- [ ] v+1:
- [ ] Support in Tegral Web Controllers
- [ ] Merge
putFundef
into put
- [ ] v+2:
- [ ] Drop annotation requirement
- [ ] Stabilization and GA
experiments Module: tegral-web-controllers