
Spring WebFlux : alternative réactive à Spring MVC
Découvrez Spring WebFlux, le framework réactif de Spring pour construire des applications web non bloquantes, performantes et scalables. Comprenez ses principes et modèles.
Introduction : Pourquoi une alternative à Spring MVC ?
Spring MVC est le framework web historique et extrêmement populaire de l'écosystème Spring. Il est basé sur l'API Servlet et suit traditionnellement un modèle synchrone et bloquant : chaque requête entrante est généralement traitée par un thread dédié qui peut être bloqué en attendant la fin d'opérations d'entrée/sortie (I/O), comme un appel à une base de données ou à un autre service web.
Bien que ce modèle soit simple à comprendre et efficace pour de nombreux cas d'usage, il peut montrer ses limites en termes de scalabilité lorsque la concurrence augmente fortement ou lorsque l'application passe beaucoup de temps à attendre des opérations I/O. Chaque thread bloqué consomme des ressources système (mémoire notamment), limitant le nombre de requêtes concurrentes pouvant être traitées efficacement.
Spring WebFlux a été introduit comme une alternative entièrement non bloquante et réactive. Il est conçu dès le départ pour gérer un grand nombre de connexions concurrentes avec un petit nombre de threads fixes, en s'appuyant sur un modèle d'exécution basé sur une boucle d'événements (event loop), similaire à Node.js. L'objectif est d'améliorer l'utilisation des ressources et la scalabilité, en particulier pour les applications I/O-intensives.
Principes fondamentaux de WebFlux
WebFlux repose sur plusieurs piliers :
- Non-bloquant : Aucune opération dans la chaîne de traitement ne doit bloquer le thread de l'event loop. Toutes les opérations potentiellement longues (I/O réseau, accès disque, etc.) doivent être asynchrones et renvoyer un résultat via un callback ou un type réactif (`Mono`/`Flux`).
- Réactif (Reactive Streams) : WebFlux implémente la spécification Reactive Streams. Cela signifie qu'il utilise des types de données (Publishers comme `Mono` et `Flux` de Project Reactor) qui représentent des flux d'événements asynchrones et gère la contre-pression (backpressure), permettant aux consommateurs de contrôler le débit des données qu'ils reçoivent pour éviter d'être submergés.
- Modèle de Concurrence (Event Loop) : Plutôt qu'un thread par requête, WebFlux utilise un petit nombre de threads (souvent égal au nombre de coeurs CPU) organisés en une boucle d'événements. Ces threads gèrent de nombreuses requêtes simultanément en traitant les événements (nouvelle requête, données disponibles, fin d'opération I/O) de manière asynchrone.
- Serveurs supportés : Par défaut, WebFlux s'exécute sur des serveurs non bloquants comme Netty (le choix par défaut et recommandé). Il peut également fonctionner sur des conteneurs de Servlets compatibles Servlet 3.1+ (comme Tomcat, Jetty, Undertow), mais Netty est généralement préféré pour exploiter pleinement le modèle non bloquant.
Deux modèles de programmation
Une caractéristique intéressante de WebFlux est qu'il offre deux modèles de programmation distincts pour définir les endpoints :
- Modèle basé sur les annotations (`@Controller`, `@RequestMapping`...) : Ce modèle est très similaire à celui de Spring MVC. Vous utilisez les mêmes annotations (`@RestController`, `@GetMapping`, `@PostMapping`, `@PathVariable`, `@RequestBody`, etc.) mais vos méthodes de contrôleur retournent des types réactifs (`Mono
` pour un résultat unique ou vide, `Flux ` pour plusieurs résultats) et peuvent accepter des paramètres de type `Mono`/`Flux`. C'est souvent le point d'entrée le plus facile pour les développeurs venant de Spring MVC. - Modèle fonctionnel (Functional Endpoints) : Ce modèle, plus léger et plus explicite, permet de définir le routage des requêtes et la logique de traitement (handlers) en utilisant des fonctions lambda. Vous définissez des `RouterFunction` qui mappent des prédicats de requête (méthode HTTP, chemin, en-têtes) à des `HandlerFunction`. Ce modèle est souvent apprécié pour sa clarté et son absence de "magie" liée aux annotations.
Ces deux modèles peuvent coexister au sein de la même application. Ils s'appuient tous deux sur les mêmes fondations réactives.
Exemple : Contrôleur annoté WebFlux
Voici à quoi ressemble un contrôleur simple utilisant le modèle annoté :
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.http.MediaType;
import java.time.Duration;
@RestController
@RequestMapping("/reactive-items")
public class ReactiveItemController {
private final ItemRepository itemRepository; // Supposons une interface de repository réactive
public ReactiveItemController(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
// Retourne un seul élément (ou vide)
@GetMapping("/{id}")
public Mono- getItemById(@PathVariable String id) {
return itemRepository.findById(id);
}
// Retourne un flux d'éléments
@GetMapping
public Flux
- getAllItems() {
return itemRepository.findAll();
}
// Accepte un élément dans le corps de la requête
@PostMapping
public Mono
- createItem(@RequestBody Mono
- itemMono) {
// La logique de traitement est aussi réactive
return itemMono.flatMap(itemRepository::save);
}
// Exemple de streaming avec Server-Sent Events (SSE)
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux
- streamItems() {
// Simule un flux d'éléments toutes les secondes
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new Item("item-" + i, "Generated Item " + i));
}
// Modèle Item simple
record Item(String id, String name) {}
// Interface Repository (exemple)
interface ItemRepository {
Mono
- findById(String id);
Flux
- findAll();
Mono
- save(Item item);
}
}
Notez comment les méthodes retournent `Mono` ou `Flux` et comment même le `@RequestBody` est encapsulé dans un `Mono`. L'ensemble de la chaîne de traitement reste non bloquant.
WebFlux vs Spring MVC : Quand choisir ?
WebFlux n'est pas destiné à remplacer complètement Spring MVC. Le choix dépend du cas d'usage :
- Choisissez WebFlux si :
- Vous développez une application qui doit gérer une très haute concurrence avec une utilisation efficace des ressources (par exemple, une passerelle API, un service avec de nombreux appels I/O non bloquants).
- Vous avez besoin de fonctionnalités de streaming (SSE, WebSockets) de manière native et performante.
- Vous interagissez principalement avec des API et des bases de données réactives (R2DBC, MongoDB Reactive, etc.).
- Votre équipe est à l'aise avec le paradigme de la programmation réactive.
- Restez avec Spring MVC si :
- Votre application est une application web traditionnelle (CRUD, rendu de vues côté serveur) avec une concurrence modérée.
- Vous dépendez fortement de bibliothèques ou d'API bloquantes (JDBC traditionnel, appels réseau bloquants) et ne pouvez pas facilement les remplacer. Utiliser WebFlux avec des appels bloquants annule ses bénéfices et peut même dégrader les performances.
- La courbe d'apprentissage de la programmation réactive est un obstacle pour votre équipe.
- La simplicité du modèle synchrone est suffisante pour vos besoins.
Il est crucial de comprendre que pour bénéficier pleinement de WebFlux, l'ensemble de la pile, de l'entrée de la requête à la sortie de la réponse (y compris les appels aux bases de données et autres services), doit être non bloquant. Intégrer des opérations bloquantes dans un flux réactif est un anti-pattern courant qui doit être évité.