1565240 7548
< < Articles

Un client REST réactif dans votre application Spring bloquante

19/06/2019

Depuis environ un an, l’équipe Spring a indiqué vouloir, à plus ou moins long terme, déprécier le RestTemplate au profit du WebClient. Pour rappel, le WebClient a été introduit avec Spring 5 et la stack réactive WebFlux. Celui-ci est construit avec Reactor et pensé pour être utilisé dans une application non-bloquante. Ceci étant, il est techniquement possible de faire cohabiter du non-bloquant et du bloquant dans une même application par exemple via la méthode block.

Nous allons ici nous interroger sur la pertinence d’une architecture applicative mélangeant une stack Spring MVC, JPA, bloquante ayant pour client REST le WebClient.

En résumé, le WebClient c’est :

  • Une alternative au bon vieux RestTemplate intégrée dans Spring WebFlux
  • Une API fluent sympathique à utiliser
  • Un client REST avec toute la puissance des pipelines réactifs (retry, timeout, asynchronisme, back pressure, etc.)

Pour approfondir tous les avantages du WebClient, n’hésitez pas à regarder la présentation à Devoxx 2019 de Stéphane Nicoll et Brian Clozel de chez Pivotal.

Comme toujours il n’y a pas de solution parfaite. Examinons maintenant quelques inconvénients de cette approche.

Inconvénient 1 : La programmation réactive c’est pas simple

Afin d’avoir accès au WebClient, la solution est d’ajouter le starter-webflux. Il s’agit ensuite d’implémenter un client REST non-bloquant comme on pourrait le faire dans une application WebFlux et de le consommer dans le code bloquant. Il y a donc ici nécessité de maîtriser un minimum les opérateurs réactifs et les concepts de base de la programmation réactive.

Rien d’insurmontable mais rappelons nous que notre objectif est “simplement” de remplacer notre client REST. De plus, on se retrouve avec une base de code contenant deux paradigmes de programmation ce qui n’est pas la panacée en terme de maintenabilité.

Inconvénient 2 : C’est du réactif, le nombre de threads va diminuer et donc faire baisser la facture cloud de moitié… Non

Pire que ça, le nombre de thread va augmenter (si si). En effet, l’ajout de la dépendance vers WebFlux va automatiquement créer un pool de thread via la formule 2 * nombre de CPU (en vrai c’est un peu plus compliqué).

Exemple sur mon application de test :

reactor-http-nio

Ce pool de thread est directement lié à Netty et ne peut être configuré que via ces deux méthodes.

Si on estime le coût d’un thread à 1 Mo, la consommation mémoire augmente donc de 8 Mo. Rien de bien grave mais c’est quelque chose à garder en tête.

Exemple d’implémentation d’un client REST réactif :

fun getHello(): Mono<String> {
    return webClient
        .get()
        .uri("/hello")
        .retrieve()
        .bodyToMono(String::class.java)
}

fun getBye(): Mono<String> {
    return webClient
        .get()
        .uri("/bye")
        .retrieve()
        .bodyToMono(String::class.java)
}

fun getHelloAndBye(): Mono<Pair<String, String>> {
    val deferredHello = getHello()
    val deferredBye = getBye()

    return deferredHello.zipWith(deferredBye) { hello, bye -> Pair(hello, bye) }
}

Exemple d’appel dans un controller Spring MVC bloquant :

val pair = reactiveApiClient.getHelloAndBye().block()

On récupère bien un objet simple et non un objet contenu dans un wrapper réactif.

Bilan intermédiaire

On constate que l’on peut plutôt facilement utiliser le WebClient dans une application Spring Java bloquante qu’elle soit legacy ou non, pourvu qu’elle ait été migrée vers Spring 5+.

Cependant, les librairies réactives proposent un grand nombre de fonctionnalités qui ne sont pas toujours nécessaires. L’idéal serait de pouvoir se passer de la lourdeur des wrappers (Mono et Flux), de la complexité du réactif et de pouvoir écrire du code impératif et non dans le style plus fonctionnel de la programmation réactive.

Le support des coroutines pour le WebClient arrive justement avec Spring 5.2 ! Celle-ci permettent justement de faire de la programmation asynchrone dans un style impératif et sans wrappers.

Les coroutines Kotlin vont-elles nous simplifier la vie ?

Oui !

Le mode opératoire est le même que pour l’approche Java + librairie réactive. À ceci près qu’on écrira notre client REST en Kotlin en utilisant des coroutines.

Attention, pensez à utiliser Spring Boot 2.2.0 M3 et à ajouter les deux dépendances ci-dessous :

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.2.1'

Celle-ci devrait être gérée par Spring Boot d’ici la release 2.2.

Exemple d’implémentation de client REST avec des coroutines :

suspend fun getHello(): String {
    return webClient
            .get()
            .uri("/hello")
            .awaitExchange()
            .awaitBody()
}

suspend fun getBye(): String {
    return webClient
            .get()
            .uri("/bye")
            .awaitExchange()
            .awaitBody()
}

suspend fun getHelloAndBye() = coroutineScope {
    val deferredHello = async {
        getHello()
    }
    val deferredBye = async {
        getBye()
    }

    awaitAll(deferredHello, deferredBye)
}

Exemple d’appel dans un controller Spring MVC bloquant :

@GetMapping("my-hello-bye")
fun getHelloAndBye() = runBlocking {
    thirdPartyApiClient.getHelloAndBye()
}

On peut très facilement faire deux appels réseaux asynchrones et attendre le résultat des deux pour continuer. Ceci dans le thread de la requête Spring MVC. On ne crée ainsi pas de thread supplémentaire. Le temps d'exécution total étant bien le temps d’exécution de la méthode la plus lente. J’ai l’impression d’avoir souvent eu ce genre de besoin et c’est la première fois que la solution technique me satisfait !

Comme pour la version réactive on pourra relativement facilement faire évoluer une app legacy écrite en Java, les deux langages étant totalement interopérables.

Bilan

Le WebClient est de façon indéniable un progrès face au RestTemplate. Celui-ci propose de nombreuses fonctionnalités ainsi qu’une API fluent très agréable à utiliser.

Dans une application Java, j’aurais tendance à choisir l’approche réactive n’étant pas un fan du mélange des langages. Dans une application Kotlin, le choix est un peu plus difficile. Les coroutines sont bien intégrées au langage et sont un très bon choix si elles couvrent les besoins. L’approche réactive serait aussi un bon choix fournissant plus de fonctionnalités au prix d’un code plus complexe et moins idiomatique.

On peut imaginer que d’ici quelques années les fibers (projet Loom) Java seront une alternative plus performantes et plus simple que la programmation réactive et les coroutines. Mais cette technologie est encore très jeune, rien n’est sûr pour le moment.