Printemps Webflux avec Kotlin

Printemps Webflux avec Kotlin

1. Vue d'ensemble

Dans ce tutoriel, nous montrons comment utiliser le module Spring WebFlux en utilisant le langage de programmation Kotlin.

Nous illustrons comment utiliser les approches de style basées sur des annotations et sur des méthodes lambda pour définir les points de terminaison.

2. Printemps WebFlux et Kotlin

La sortie de Spring 5 a introduit deux nouvelles fonctionnalités majeures, parmi lesquelles le support natif du paradigme de la programmation réactive et la possibilité d’utiliser le langage de programmation Kotlin.

Tout au long de ce tutoriel, nous supposons que nous avons déjà configuré l'environnement (consultezone of our tutorials sur ce problème) et comprenons la syntaxe du langage Kotlin (another tutorial sur le sujet).

3. Approche basée sur l'annotation

WebFlux nous permet de définir les points de terminaison qui doivent gérer les demandes entrantes de la manière bien connue en utilisant les annotations du framework SpringMVC comme@RequestMapping ou@PathVariable ou des annotations de commodité comme@RestController ou@GetMapping.

Malgré le fait que les noms d'annotations soient les mêmes, lesWebFlux’s font que les méthodes sontnon-blocking.

Par exemple, ce noeud final:

@GetMapping(path = ["/numbers"],
  produces = [MediaType.APPLICATION_STREAM_JSON_VALUE])
@ResponseBody
fun getNumbers() = Flux.range(1, 100)

produit un flux de quelques premiers entiers. Si le serveur fonctionne surlocalhost:8080, connectez-vous à celui-ci au moyen de la commande:

curl localhost:8080/stream

imprimera les numéros demandés.

4. Approche Lambda

Une nouvelle approche dans la définition des points de terminaison consiste à utiliser des expressions lambda présentes en Java depuis la version 1.8. Avec l'aide de Kotlin, les expressions lambda peuvent être utilisées même avec les versions antérieures de Java.

Dans WebFlux, les fonctions du routeur sont des fonctions déterminées par unRequestPredicate (en d'autres termes, qui doit gérer la requête) et unHandlerFunction (en d'autres termes, comment la requête doit être élaborée).

Une fonction de gestionnaire accepte une instance deServerRequest et produit une instance deMono<ServerResponse>.

Dans Kotlin, if the last function argument is a lambda expression, it can be placed outside the parentheses.

Une telle syntaxe nous permet de mettre en évidence la division entre le prédicat de la demande et la fonction de gestionnaire

router {
    GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
}

pour les fonctions de routeur.

La fonction a un format clair et lisible par l’homme: dès qu’une requête de type GET arrive sur / route, créez une réponse (en ignorant le contenu de la requête - d’où le trait de soulignement du symbole) avec le statut HTTP OK et un corps construit à partir du objet donné.

Maintenant, afin de le faire fonctionner dans WebFlux, nous devrions placer la fonction de routeur dans une classe:

@Configuration
class SimpleRoute {

    @Bean
    fun route() = router {
        GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
    }
}

Souvent, la logique de nos applications exige que nous construisions des fonctions de routeur plus sophistiquées.

Dans WebFlux, la fonction de routeur DSL de Kotlin définit une variété de fonctions telles queaccept,and,or,nest,invoke,GET,POST au moyen deextension functions qui nous permet de construire des fonctions de routeur composites:

router {
    accept(TEXT_HTML).nest {
        (GET("/device/") or GET("/devices/")).invoke(handler::getAllDevices)
    }
}

La variablehandler doit être une instance d'une classe implémentant une méthodegetAllDevices() avec la signature standardHandlerFunction:

fun getAllDevices(request: ServerRequest): Mono

comme nous l'avons mentionné ci-dessus.

Afin de maintenir une séparation appropriée des problèmes, nous pouvons placer les définitions de fonctions de routeur non liées dans des classes distinctes:

@Configuration
class HomeSensorsRouters(private val handler: HomeSensorsHandler) {
    @Bean
    fun roomsRouter() = router {
        (accept(TEXT_HTML) and "/room").nest {
            GET("/light", handler::getLightReading)
            POST("/light", handler::setLight)
        }
    }
    // eventual other router function definitions
}

Nous pouvons accéder aux variables de chemin au moyen de la méthodeString-valuedpathVariable():

val id = request.pathVariable("id")

tandis que l'accès au corps deServerRequest est réalisé au moyen des méthodesbodyToMono etbodyToFlux, soit:

val device: Mono = request
  .bodyToMono(Device::class.java)

5. Essai

Afin de tester les fonctions du routeur, nous devons construire une instanceWebTestClient des routeursSimpleRoute().route() que nous voulons tester:

var client = WebTestClient.bindToRouterFunction(SimpleRoute().route()).build()

Nous sommes maintenant prêts à tester si la fonction de gestionnaire du routeur renvoie l'état OK:

client.get()
  .uri("/route")
  .exchange()
  .expectStatus()
  .isOk

L'interface WebTestClient définit les méthodes qui nous permettent de tester toutes les méthodes de requête HTTP commeGET,POST etPUT même sans exécuter le serveur.

Afin de tester le contenu du corps de la réponse, nous pouvons vouloir utiliser la méthodejson():

client.get()
  .uri("/route")
  .exchange()
  .expectBody()
  .json("[1, 2, 3]")

6. Conclusion

Dans cet article, nous avons montré comment utiliser les fonctionnalités de base du framework WebFlux avec Kotlin.

Nous avons brièvement mentionné l'approche bien connue basée sur les annotations dans la définition des points d'extrémité et consacré plus de temps à illustrer comment les définir à l'aide de fonctions de routeur dans une approche de style lambda.

Tous les extraits de code peuvent être trouvés dans notre référentielover on GitHub.