I find the routing in ktor to be less clear than I...
# ktor
h
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
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
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:
Copy code
// 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
Thanks, I'll look into it again then ...
c
@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
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
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:
Copy code
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:
Copy code
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:
Copy code
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:
Copy code
fun Application.testMain() {
    // Install Ktor features here...
    routing {
        homeRoute()
        setupOwnerRoutes(true)
        setupSiteRoutes(true)
        setupSensorDataRoutes(true)
    }
}