Spring without Annotations

FILE : FunctionalWeb1.kt

This example shows really simple mapping and running small application with new "functional rest configuration".

Lets start from _routes _configutration.

fun helloRouterFunction(): RouterFunction<ServerResponse> {
    //This one uses official kotlin DSL for building Routerfunction and Handlerfunctions
    return router {
        GET("/hello") { _ ->
            //and below is HandlerFunction
            ok().body(just("Hello World!"), String::class.java)
        }
    }
}

First of all you need to use dedicated Kotlin DSL "org.springframework.web.reactive.function.server.router" instead of what you can find in most Java WebFlux tutorials "org.springframework.web.reactive.function.server.RouterFunctions.route". With the DSL you will be forced to write less boilerplate code.

Functions used "router" - like GET - works similarly and build their own DSL with "receiver" syntax - so everything in brackets is actually just a dsl instance method

return router {
        this.GET("/hello") { _ ->
            //and below is HandlerFunction
            ok().body(just("Hello World!"), String::class.java)
        }
}

Context

When we have routing configuration we need to add it to the main spring context, take a look :

val ctx = GenericApplicationContext{
            // Explain kotlin dsl
            beans {  //1
                bean("webHandler"){ //2
                    RouterFunctions.toWebHandler(router) //<- inject our routing into spring component
                }
            }.initialize(this) //3
            refresh()
}
  1. Similarly to Routing DSL there is a dedicated Kotlin dsl for bean definitions.
  2. "bean" is just a method call on BeansDsl . Here we are wrapping our routes to a handler which can be used by server
  3. This is like classic "build" method in plain java builders.

Server

Last step is to just start a server on predefined port.

//reactory library
val server = HttpServer.create(port)

//spring library - adapter
val httpHandler = WebHttpHandlerBuilder.applicationContext(ctx).build()
server.startAndAwait(ReactorHttpHandlerAdapter(httpHandler))

Beans

FILE : FunctionalWeb2.kt

This time we are going to see an example of defining and injecting a dependency with KotlinDsl.

our test subject is symbolic repository:

//BEAN interface
interface RepositoryExample2 {
    fun find(id: Int): Mono<String>
}

//BEAN implementation
object InMemoryRepo : RepositoryExample2 {
    override fun find(id: Int): Mono<String> = if (id == 1) Mono.just("SUCCESS") else Mono.empty()
}

Injection

It is very easy and uses special Kotlin mechanism called "refined types".

beans {
            bean<InMemoryRepo>() //1
            bean("webHandler") {
                val repo= ref<InMemoryRepo>() //2
                RouterFunctions.toWebHandler(routing(repo)) //3
            }
}

Three easy steps

  1. Define a bean
  2. Retrieve a bean
  3. pass a bean as dependency

Parameters in url

GET("/find/{id}") { request ->  //1
                val response = Mono.justOrEmpty(request.pathVariable("id")) //2
                        .map(Integer::valueOf)
                        .flatMap(repo::find)
                ok().body(response, String::class.java)  //3
            }
  1. We use placeholder '{id}' in url
  2. Now we can retrieve part mapped with given placeholder. We wrapped this call with Mono.justOrEmpty in case id will be empty
  3. If our response will be 'Mono.empty' server response with 404, if it is 'Mono.error' then status is 50X .

Routing

FILE : FunctionalWebRouting.kt

In This example we will focus more one building more complex REST endpoint. First of all - we will take a look at will be handler which is invoked when route is matched : [{PREDICATE} , {HANDLER}]

val helloHandler: (ServerRequest) -> Mono<ServerResponse> =
            { ServerResponse.ok().body(Mono.just("Example3"), String::class.java) }

We can see that handler is just a function Request -> Mono<Response> . Mono signalise that response will be generated asynchronously.

Take a notice that in the example below we are also passing Mono to the body method. This is very handy when our response generation is be blocking. Yet, if we just want to return simple value there is simpler synchronous variant .

val helloHandler2: (ServerRequest) -> Mono<ServerResponse> =
            { ServerResponse.ok().syncBody("Example3") }

Predicate

Situation is a little bit different with predicates. Technically GET or POST are methods of a RouterFunctionDsl. So technically we need an instance of a DSL to invoke them, something like this

//just for ilustration
val dsl = RouterFunctionDsl()
val requestPredicate: RequestPredicate = (dsl.GET("/example3").or(dsl.GET("/hello3")))

But the better way would be to just create predicate in a method which works in a context of specific DSL instance

fun createPredicate(dsl:RouterFunctionDsl): RequestPredicate =
            (dsl.GET("/example3").or(dsl.GET("/hello3")))

Now - only in context of a dsl - we can call invoke method

val routing=router {
        requestPredicate.invoke(helloHandler)
        ...
        }

Why it is limited only to dsl context? If we will look at an invoke method declaration , we will see that it is dependant on context state

operator fun RequestPredicate.invoke(f: (ServerRequest) -> Mono<ServerResponse>) {
        routes += RouterFunctions.route(this, HandlerFunction { f(it) })
}

So there is no sense to call it without access to routes field. There are also two additional things worth noticing here

  1. operator key word allow us to ignore invoke name and just call requestPredicate(helloHandler)
  2. This example nicely illustrate how to create context dependant extension methods which can enrich our DSLs

Nesting

//EXPLAIN WHY INVOKE ONLY WORKS WITHIN router
val routing=router {
        requestPredicate.invoke(helloHandler)
        //below is overloaded extension function. Not the best Idea
        accept(MediaType.APPLICATION_JSON).nest { POST("/example3json", jsonRoute) } //1
        "/root".nest {    //2
            "/service".nest { //2
              accept(MediaType.TEXT_HTML).nest { //1
                  GET(""){ServerResponse.ok().body("deeplyNestedService".toMono() , String::class.java)}
              }
            }
        }
}
  1. We can nest predicates. So in our example we can add subtree for a case when media type was JSON or plain HTML. Just be aware that this nest is an extension method from dsl - NOT _nest _method from predicate itself !
  2. There are also extension functions for strings to easier build complex matching for paths

Filters

You can attach a filter to mapped routing :

 val filterFunction: (ServerRequest, HandlerFunction<ServerResponse>) -> Mono<ServerResponse> = { request, next ->
        println("start filtering")
        val response = next.handle(request)
        response
 }

It differs from previous functions because it is so called BiFunction. As arguments it receives not only request but also next handler which you can invoke inside your filter. So for example you can easily reject further processing in case of wrong authentication.

Exercises

FILE : SpringPart1Exercises

In this exercise you need to implement routes :

 private fun createRouting(repo: Repo) = router {
        GET("/exercise1get"){_ ->
            TODO() //<---------EXERCISE
        }
        "/datastore".nest {
            PUT("/user/{id}"){r ->
               TODO() //
            }
            GET("/users"){
                TODO() //<---------EXERCISE
            }
        }
    }

Use tests from FILE : SpringPart1JavaTest as your specification . During workshops we will explain why for now you need to write web tests in Java.

results matching ""

    No results matching ""