https://kotlinlang.org logo
#ktor
Title
m

mp

04/11/2018, 3:38 PM
I'm just starting out with ktor so maybe I'm missing something obvious, but I'm wondering how I can go about structuring more complex endpoints across classes, and the "Extracting Routes" code sample appears to be identical to the starter Hello World above it. https://ktor.io/servers/structure.html 🤔 What I'd like would be a way to have a class (an implementation of
PipelineInterceptor
?) handle processing for a given path.
d

Deactivated User

04/11/2018, 4:06 PM
Do you mean to have several methods per class handling each method one route? Or do you mean to have a class per route?
m

mp

04/11/2018, 4:07 PM
I'd be fine with a class per route. I'm tinkering with the guice sample, but my goal is to have a nice way of packaging up logic that isn't so tied to lexical scoping. That's great for small services, but I'm trying to feel out how it scales complexity wise.
I'm sure there are other approaches, but what comes to mind is to have a SAM type with the appropriate method signature
that way I could instantiate the class by hand if it's simple, or ask a Guice
Injector
for it if appropriate
Anyway, I'm not tied to a particular structure; I'm just looking for some way to grow past a bunch of (convenient) local variables and closures 🙂
Bonus points for being friendly to DI tools like Guice.
d

Deactivated User

04/11/2018, 4:15 PM
So the problem with replicating as the
HelloRoutes
from the dice example is that you have to declare routes in code instead of with methods? Your objective is being able to call those methods/routes directly for testing?
I have an experiment (totally unofficial and unsupported), but for illustrate it. Are you searching for something like this? https://github.com/soywiz/ktor-springer#basic-usage I’m trying to understand your use case. You can scale as much as needed with extension methods or plain Dice classes splitting them in several files.
m

mp

04/11/2018, 4:20 PM
No, I think the HelloRoutes way is manageable, it's just a bit clumsy. I'm totally fine with declaring routes in code rather than via annotations. My objective is to be able to package up complexity (and use DI to avoid having to grow the lexical scope). It's possible this is a bad idea for reasons I haven't thought of yet, but it seems like I want to be able to do something like
Copy code
get("/foo", someInstance)
.
One thing I don't love about the
HelloRoutes
approach is that it leaks
this
before construction is finished, which is a thread safety problem.
ktor-springer
is definitely interesting, and I'm glad to see the ktor model is flexible enough to support that (even in an experimental way)
I don't want to twist Ktor into reimplementing JAX-RS, but I do like not being tied into such a closure-focused way of binding logic to paths
I guess a different way of stating the problem is that I don't understand what's going on with the type system well enough to replace the
suspend
closure being passed to
get()
and friends with an instance of a class.
d

Deactivated User

04/11/2018, 4:27 PM
Uhm. AFAIK once you reach the init constructor, the previous constructor (the one with the injection) has already been called. So every dependency should be instantiated already. And be already part of your object as a final member. As long as you declare other properties before the init you should be okay. But not sure if that’s your concern. Since I don’t know why it is thread unsafe. --- What about?
Copy code
class HelloRoutes @Inject constructor(application: Application, @Named("hello-message") message: String) {
    init {
        application.routing {
            get("/") { root() }
        }
    }
    
    suspend fun PipelineContext<Unit, ApplicationCall>.root() {
        // ...
    }
}
m

mp

04/11/2018, 4:28 PM
Ah, if
init
runs after the write barrier for final fields, then it should be safe.
I think I should not take any more of your time then yet while I play with this and see if I can make it do what I want. Thanks for your attention; it's been very helpful. 🙂
d

Deactivated User

04/11/2018, 4:30 PM
Don’t worry. Those questions totally make sense. So feel free to ask as much as needed! Also I’m going to review the structure page to give more information and to provide guide examples
👍 1
m

mp

04/11/2018, 4:32 PM
The issue I have with solely using extension methods as a means of structuring things is that it still ultimately depends on lexical scope as a means of sharing resources. The
HelloResource
-style piggybacking on top of Guice's instantiation to register with the
Application
is a little unintuitive IMO but nothing I can't get used to, and it certainly has the real benefit of being very explicit. You have somewhere to put breakpoints, etc, and that's always a problem with annotation-based approaches.
d

Deactivated User

04/11/2018, 4:45 PM
Aha. Yeah I agree that initially is a bit strange when coming from other approaches. It is more explicit and relies less on reflection for doing the job, so it is less magical. If after trying it a bit you have ideas, suggestions or feedback; let us now. --- Also if you don’t want to use extension methods, you can do something like this so you can extract the body handler of your routes:
Copy code
init {
    application.routing {
        get("/") { root(call) }
    }
}

suspend fun root(call: ApplicationCall) {
    call.respondText("HELLO")
}
2 Views