https://kotlinlang.org logo
Title
h

hallvard

09/30/2019, 7:20 AM
I find the routing in ktor to be less clear than I like. Coming from the play framework, I may be spoiled by the very straightforward routes file over there. Are there any tricks I should know about that could ease my life here? (A 200-endpoint routes file actually is no problem for a human eye to parse in the Play framework, but reading the same amount of endpoints from a routing {} DSL in ktor is less so ...)
a

atsushi-koshikizawa

09/30/2019, 8:05 AM
How about these? 1. Use
@Location
, not to use
routing
to define URL path • then you can find which function to be called by the request. 2. Define
@Location
and route (
get
,
post
, ...) one-to-one. 3. Do not nest routing • then you can refer request parameters in the same way 4. Split routing definition. (see snippet) ( https://tech.bizreach.co.jp/posts/324/ktor-routing/ )
p

Pavel Lechev

09/30/2019, 8:39 AM
You do not need to store all endpoints in the same file. It is easy and viable to split them in logical classes, e.g.
User
,
Product
,
...
In each of those, you declare an extension function (e.g.
fun Application.user()
,
fun Application.product()
) which then uses the
routing { ...}
DSL for defining the relevant endpoints. Each of these functions is then becoming a Ktor module which is easy to install with
module { ... }
and test with
withTestApplication({ ...})
.
Here is a rather contrived example:
// file User.kt
object User {

    fun Application.user() {
        routing {
            get("/profile") {
                // get user
            }
            post("/profile") {
                // create user
            }
        }
    }
}

// file Product.kt
object Product {

    fun Application.product() {
        routing {
            get("/product") {
                // get product
            }
            post("/product") {
                // create product
            }
        }
    }
}

// file Application.kt
object Application {

    @JvmStatic
    fun main(args: Array<String>) {
        val env = applicationEngineEnvironment {
            module {
                user()
                product()
            }
        }
        embeddedServer(Netty, env).start(true)
    }
}


// inside the test file ... testing User endpoints only
@Test
fun `testCreateUser`() = withTestApplication({
    user()
}) {
    handleRequest(<http://HttpMethod.Post|HttpMethod.Post>, "/profile") {
        // configure request
    }.also {
        // assert response ...  
    }
}
👍 2
h

hallvard

09/30/2019, 11:10 AM
Thanks, I'll look into it again then ...
c

cy

09/30/2019, 3:39 PM
@hallvard Have you resolved your questions? Let me know if there are still open questions about the DSL or ideas about how to improve and make migration from Play more intuitive
h

hallvard

09/30/2019, 4:12 PM
I've been elsewhere today, and probably will be for a few days. But I'll update here if I get a brilliant idea, haha.
n

napperley

09/30/2019, 8:52 PM
Below is a example of how to easily manage routing in Ktor. In my application there is a separate Application module called main (in the server.kt file) that sets up the routing by delegating the routing logic to a separate routing file, in a flat DSL style, eg:
fun Application.main() {
    // Install Ktor features here...
    routing {
        homeRoute()
        setupOwnerRoutes()
        setupSiteRoutes()
        setupSensorDataRoutes()
    }
}
In the routing file (http_routing.kt) there is a "Route.setupxxRoutes" extension function for each REST resource, eg:
internal fun Route.setupSensorDataRoutes(testMode: Boolean = false) = route("/sensorData") {
    timestampCountRoute(testMode)
    sensorDataSiteChannelRoute(testMode)
    sensorDataTimestampRangeRoute(testMode)
}
Each route corresponds to a private "Route.xxRoute" extension function (defined in http_routing.kt), eg:
private fun Route.timestampCountRoute(testMode: Boolean = false) = get("/timestampCount") {
    val count = if (testMode) 0 else Database.timestampCount()
    call.respond(mapOf("total" to count))
}
The beauty of this setup/architecture is that it is simple (heads down the explicit development route without using reflection or annotations), doesn't require OOP techniques to be used (like defining a Class, Object etc), and is loosely coupled (routing logic is separate from server configuration).
Forgot to mention that there is a separate Application module called testMain (defined in the test_extensions.kt file that located in the test source set), which reuses the routing logic, eg:
fun Application.testMain() {
    // Install Ktor features here...
    routing {
        homeRoute()
        setupOwnerRoutes(true)
        setupSiteRoutes(true)
        setupSensorDataRoutes(true)
    }
}