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

Explore the possibility of "functional definitions" in DI #65

Open
7 of 10 tasks
utybo opened this issue Dec 28, 2022 · 1 comment
Open
7 of 10 tasks

Explore the possibility of "functional definitions" in DI #65

utybo opened this issue Dec 28, 2022 · 1 comment
Labels
experiments This issue or PR is about an experimental (or would-be experimental) feature Module: tegral-di Module: tegral-web-controllers
Milestone

Comments

@utybo
Copy link
Owner

utybo commented Dec 28, 2022

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

@utybo utybo added experiments This issue or PR is about an experimental (or would-be experimental) feature Module: tegral-web-controllers labels Dec 28, 2022
@utybo utybo added this to the 0.0.4 milestone Dec 28, 2022
@utybo utybo modified the milestones: 0.0.4, 0.0.5 Feb 8, 2023
@utybo utybo mentioned this issue Jun 11, 2023
3 tasks
@utybo
Copy link
Owner Author

utybo commented Aug 23, 2023

Idea for the step 3 of this whole plan, I think it would be better if we use the following behavior.

In cases where we could have a fundef:

  • If a @Fundef annotation is present, register as a fundef,
  • provide, in the tegralDi {} block, some function defineAsFundefIf { ... } that takes the incoming function and, if it returns true true, will register it as a fundef as if a @Fundef annotation was present. Multiple defineAsFundefIf can be added and, if any of them evaluates to true, the function will be registered as a fundef
  • keep the current behavior (only accept functions with no parameters or 1 argument of type InjectionScope)

@utybo utybo modified the milestones: 0.0.5, TBD Aug 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
experiments This issue or PR is about an experimental (or would-be experimental) feature Module: tegral-di Module: tegral-web-controllers
Projects
None yet
Development

No branches or pull requests

1 participant