Spring Webflux – Kotlin DSL – a walkthrough of the implementation
In a previous blog post I had described how Spring Webflux, the reactive programming support in Spring Web Framework, uses a Kotlin based DSL to enable users to describe routes in a very intuitive way. Here I wanted to explore a little of the underlying implementation.
A sample DSL describing a set of endpoints looks like this:
package sample.routes import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.MediaType.APPLICATION_JSON import org.springframework.web.reactive.function.server.router import sample.handler.MessageHandler @Configuration class AppRoutes(private val messageHandler: MessageHandler) { @Bean fun apis() = router { (accept(APPLICATION_JSON) and "/messages").nest { GET("/", messageHandler::getMessages) POST("/", messageHandler::addMessage) GET("/{id}", messageHandler::getMessage) PUT("/{id}", messageHandler::updateMessage) DELETE("/{id}", messageHandler::deleteMessage) } } }
To analyze the sample let me start with a smaller working example:
import org.junit.Test import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.web.reactive.function.server.ServerResponse.ok import org.springframework.web.reactive.function.server.router class AppRoutesTest { @Test fun testSimpleGet() { val routerFunction = router { GET("/isokay", { _ -> ok().build() }) } val client = WebTestClient.bindToRouterFunction(routerFunction).build() client.get() .uri("/isokay") .exchange() .expectStatus().isOk } }
The heart of the route definition is the “router” function:
import org.springframework.web.reactive.function.server.router ... val routerFunction = router { GET("/isokay", { _ -> ok().build() }) }
which is defined the following way:
fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl().apply(routes).router()
The parameter “routes” is a special type of lambda expression, called a Lambda expression with a receiver. This means that in the context of the router function, this lambda expression can only be invoked by instances of “RouterFunctionDsl” which is what is done in the body of the function using apply method, this also means in the body of the lambda expression “this” refers to an instance of “RouterFunctionDsl”. Knowing this opens up access to the methods of “RouterFunctionDsl” one of which is GET that is used in the example, GET is defined as follows:
fun GET(pattern: String, f: (ServerRequest) -> Mono<ServerResponse>) { ... }
There are other ways express the same endpoint:
GET("/isokay2")({ _ -> ok().build() })
implemented in Kotlin very cleverly as:
fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern) operator fun RequestPredicate.invoke(f: (ServerRequest) -> Mono<ServerResponse>) { ... }
Here GET with the pattern returns a “RequestPredicate” for which an extension function has been defined (in the context of the DSL) called invoke, which is in turn a specially named operator.
Or a third way:
"/isokay" { _ -> ok().build() }
which is implemented by adding an extension function on String type and defined the following way:
operator fun String.invoke(f: (ServerRequest) -> Mono<ServerResponse>) { ... }
I feel that the Spring Webflux makes an excellent use of the Kotlin DSL in making some of these route definitions easy to read while remaining concise.
This should provide enough primer to explore the source code of Routing DSL in Spring Webflux .
My samples are available in a github repository here – https://github.com/bijukunjummen/webflux-route-with-kotlin
Reference: | Spring Webflux – Kotlin DSL – a walkthrough of the implementation from our JCG partner Biju Kunjummen at the all and sundry blog. |