OK
Spring + Kotlin 2 : konfiguracja REST

Spring + Kotlin 2 : konfiguracja REST

Spring + Kotlin 2 : konfiguracja REST

Poprzednie odcinki

W poprzednim odcinku zobaczyliśmy jak użycie Kotlina w Springu 5 ułatwia konfigurację i uwalnia nas od potrzeby używania adnotacji. Dzisiaj skupimy się na konfiguracji prostego RESTa z użyciem nowej tzw. “Funkcyjnej” składni.

Uważaj na przykłady Javy

Przede wszystkim czytając tutoriale do nowego mechanizmu – jak na przykład ten https://spring.io/blog/2016/09/22/new-in-spring-5-functional-web-framework – uważaj na użytą składnie Javy gdyż używając w identyczny sposób Kotlina możesz napotkać na pewne trudności.Otóż API dla Javy zaleca użycie funkcji “route”, która jest ona zdefiniowana w sposób następujący:

public static <T extends ServerResponse> RouterFunction<T> route(
     RequestPredicate predicate, HandlerFunction<T> handlerFunction) {

  return new DefaultRouterFunction<>(predicate, handlerFunction);
}

Gdzie :

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
  Mono<T> handle(ServerRequest request);
}

Używajac tego API możemy wygodnie wpisać sobie lambdę tam gdzie oczekiwany jest HandlerFunction a to dlatego, iż Java z automatu konwertuje ową lambdę na tzw. SAM czyli klasę abstrakcyjną/interfejs z jedną metodą. Dzięki temu możemy sobie napisać route(GET(“/”),r->…) . Niestety w Kotlinie przy konwersji na typ Javy jest potrzebna dodatkowa podpowiedź dla kompilatora:

//route(GET("/test"), { r -> ok().body(Mono.just("response")) })   <- to nie pyknie

//poniżej podajemy podpowiedź, że ta lambda to HandlerFunction i działa
   route(GET("/test"), HandlerFunction { r -> ok().body(Mono.just("response")) })

Dlatego też warto używać dedykowany DSL udostępniony dla Kotlina. Konstrukcja tam użyta jest podobna do tego co widzieliśmy w poprzednim odcinku dla definicji Beanów – czyli dla przypomnienia:

fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
  val beans = BeanDefinitionDsl()
  beans.init()
  return beans
}

W przypadku definiowania “routingu” wygląda to bardzo ale to bardzo podobnie :

fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl().apply(routes).router()

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

Jak możecie chyba zgadnąć GET to nic innego wywołanie metody na naszym DSLu i aby było jawne, jasne i klarowne co tam się dzieje to dzieję coś takiego :

router {
   val dsl:RouterFunctionDsl = this  //<- o tutaj cała magia
   dsl.GET("/hello") { _ ->
       //and below is HandlerFunction
       ok().body(just("Hello World!"), String::class.java)
   }
} 

W powyższym przykładzie GET jest po prostu wygodnym opakowaniem na to API dla Javy, o którym mówiliśmy na samym początku :

fun GET(pattern: String, f: (ServerRequest) -> Mono<ServerResponse>) {
  routes += RouterFunctions.route(RequestPredicates.GET(pattern), 
  HandlerFunction { f(it) })
}

I problem “bojlerplejtu” rozwiązany.

Mono

Zwróć uwagę na sygnaturę funkcji przekazanej do Handlera:

f: (ServerRequest) -> Mono<ServerResponse>

Czym jest to Mono i jak wpływa na przetwarzanie requestu? O tym w następnym odcinku…

ul. Jaracza 62
90-251 Łódź
Bitnami