When declaring RouterFunctions with RouterFunctionDsl, in order to inject required beans, we are forced to use the slightly verbose beanProvider from the outer scope of the SupplierContextDsl. This means that code which defines the routes is also implicitly coupled to usage within that context. For example:

BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean {
        router {
            GET("/baz") {
                ok().header("hello", beanProvider<InjectedBean>().getObject().sayHi()).build()
            }
        }
    }
}

This presents a problem when we attempt to decompose our DSL usage. For example, this does not work

val myRoutes  = { bean : InjectedBean  -> {
        router {
            GET("/baz") { ok().header("hello", bean.sayHi()).build() }
        }
    }
}
BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean(::routes)
}

In order to make it work, the router DSL must be extracted as

val routes: (BeanRegistrarDsl.SupplierContextDsl<RouterFunction<ServerResponse>>) -> RouterFunction<ServerResponse> = { context ->
        router {
            GET("/baz") { ok().header("hello", context.beanProvider<InjectedBean>().getObject().sayHi()).build() }
        }
    }

Which is verbose and clunky. With some extension functions, we can create the experience for injection to be like this:

BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean {
        routes { bean: InjectedBean ->
            {
                GET("/baz") { ok().header("hello", bean.sayHi()).build() }
            }
        }
    }
})

or, with decomposition:

val myRoutes = router { bean: InjectedBean ->
    {
        GET("/baz") { ok().header("hello", bean.sayHi()).build() }
    }
}
BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean {
        myRoutes()
    }
})

I will open a draft PR to show how this could be implemented

Comment From: bclozel

@wakingrufus please don't, let's discuss this enhancement request first and then consider the changes to be made. Thanks!

Comment From: wakingrufus

@wakingrufus please don't, let's discuss this enhancement request first and then consider the changes to be made. Thanks!

Ok sounds good. I did not open a PR, but I did push a branch to my fork that references this issue, so if people are interested in a concrete example, they can follow that reference to see it. Thanks for taking a look! I'd like to tag @sdeleuze on this as well, as we have discussed the idea informally, and he is the primary author of the related DSLs

Comment From: wakingrufus

I think the major drawback to this is that you need a separate function for each number of parameters (Arity). But since these are convenience methods / syntactic sugar, I think it is worth it if just the majority of cases are covered. In my experience 3 is reasonable. after that, the handler is probably doing too much and that logic should be delegated to a single Service class

Comment From: wakingrufus

I toyed around a little more with it, and I think maybe hiding the calls to the SupplierContextDsl under the hood of these functions is too much abstraction. instead, we could let the caller make those invocations:

inline example

val beans = BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean {
        router(bean()) { bean: InjectedBean ->
            {
                GET("/baz") { ok().header("hello", bean.sayHi()).build() }
            }
        }
    }
})

decomposed example:

val myRoutes = router { bean: InjectedBean ->
    {
        GET("/baz") { ok().header("hello", bean.sayHi()).build() }
    }
}
val beans = BeanRegistrarDsl({
    registerBean<InjectedBean>()
    registerBean {
        myRoutes(bean())
    }
})