Maîtriser le framework Spring 5, partie 2: Spring WebFlux

Spring WebFlux introduit le développement Web réactif dans l'écosystème Spring. Cet article vous permettra de démarrer avec les systèmes réactifs et la programmation réactive avec Spring. Vous découvrirez d'abord pourquoi les systèmes réactifs sont importants et comment ils sont implémentés dans Spring Framework 5, puis vous obtiendrez une introduction pratique à la création de services réactifs à l'aide de Spring WebFlux. Nous allons créer notre première application réactive à l'aide d'annotations. Je vais également vous montrer comment créer une application similaire à l'aide des nouvelles fonctionnalités fonctionnelles de Spring.

Tutoriels Spring sur JavaWorld

Si vous êtes nouveau dans le framework Spring, je vous recommande de commencer par l'un des tutoriels précédents de cette série:

  • Qu'est-ce que le printemps? Développement basé sur des composants pour Java
  • Maîtriser le framework Spring 5: Spring MVC

Systèmes réactifs et Spring WebFlux

Le terme réactif est actuellement populaire auprès des développeurs et des responsables informatiques, mais j'ai remarqué une certaine incertitude sur ce qu'il signifie réellement. Pour mieux comprendre ce que sont les systèmes réactifs, il est utile de comprendre le problème fondamental qu'ils sont censés résoudre. Dans cette section, nous parlerons des systèmes réactifs en général, et je présenterai l'API Reactive Streams pour les applications Java.

Évolutivité dans Spring MVC

Spring MVC a gagné sa place parmi les meilleurs choix pour la création d'applications Web Java et de services Web. Comme nous l'avons découvert dans Mastering Spring Framework 5, partie 1, Spring MVC intègre de manière transparente les annotations dans l'architecture robuste d'une application Spring. Cela permet aux développeurs familiers avec Spring de créer rapidement des applications Web satisfaisantes et hautement fonctionnelles. L'évolutivité est cependant un défi pour les applications Spring MVC. C'est le problème que Spring WebFlux cherche à résoudre.

Framework Web bloquants ou non bloquants

Dans les applications Web traditionnelles, lorsqu'un serveur Web reçoit une demande d'un client, il accepte cette demande et la place dans une file d'attente d'exécution. Un thread dans le pool de threads de la file d'attente d'exécution reçoit alors la demande, lit ses paramètres d'entrée et génère une réponse. En cours de route, si le thread d'exécution a besoin d'appeler une ressource de blocage - telle qu'une base de données, un système de fichiers ou un autre service Web - ce thread exécute la requête de blocage et attend une réponse. Dans ce paradigme, le thread est effectivement bloqué jusqu'à ce que la ressource externe réponde, ce qui entraîne des problèmes de performances et limite l'évolutivité. Pour lutter contre ces problèmes, les développeurs créent des pools de threads de taille généreuse, de sorte que lorsqu'un thread est bloqué, un autre thread puisse continuer à traiter les demandes. La figure 1 montre le flux d'exécution d'une application Web bloquante traditionnelle.

Steven Haines

Les frameworks Web non bloquants tels que NodeJS et Play adoptent une approche différente. Au lieu d'exécuter une demande de blocage et d'attendre qu'elle se termine, ils utilisent des E / S non bloquantes. Dans ce paradigme, une application exécute une requête, fournit du code à exécuter lorsqu'une réponse est renvoyée, puis rend son thread au serveur. Lorsqu'une ressource externe renvoie une réponse, le code fourni sera exécuté. En interne, les frameworks non bloquants fonctionnent à l'aide d'une boucle d'événements. Dans la boucle, le code d'application fournit soit un rappel, soit un futur contenant le code à exécuter lorsque la boucle asynchrone se termine.

Par nature, les frameworks non bloquants sont pilotés par les événements . Cela nécessite un paradigme de programmation différent et une nouvelle approche du raisonnement sur la manière dont votre code sera exécuté. Une fois que vous avez bien réfléchi, la programmation réactive peut conduire à des applications très évolutives.

Rappels, promesses et futurs

Au début, JavaScript gérait toutes les fonctionnalités asynchrones via des rappels . Dans ce scénario, lorsqu'un événement se produit (par exemple, lorsqu'une réponse d'un appel de service devient disponible), le rappel est exécuté. Bien que les rappels soient toujours répandus, la fonctionnalité asynchrone de JavaScript est plus récemment passée aux promesses . Avec des promesses, un appel de fonction revient immédiatement, renvoyant une promesse de livrer les résultats à un moment ultérieur. Plutôt que des promesses, Java implémente un paradigme similaire en utilisant des futurs . Dans cet usage, une méthode renvoie un futur qui aura une valeur à un moment donné dans le futur.

Programmation réactive

Vous avez peut-être entendu le terme programmation réactive lié aux cadres et outils de développement Web, mais qu'est-ce que cela signifie vraiment? Le terme tel que nous l'avons appris provient du Manifeste Réactif, qui définit les systèmes réactifs comme ayant quatre traits principaux:

  1. Les systèmes réactifs sont sensibles , ce qui signifie qu'ils répondent en temps opportun, dans toutes les circonstances possibles. Ils se concentrent sur la fourniture de temps de réponse rapides et cohérents, établissant des limites supérieures fiables afin d'offrir une qualité de service constante.
  2. Les systèmes réactifs sont résilients , ce qui signifie qu'ils restent réactifs face à une défaillance. La résilience est obtenue grâce aux techniques de réplication, de confinement, d'isolement et de délégation. En isolant les composants d'application les uns des autres, vous pouvez contenir les pannes et protéger le système dans son ensemble.
  3. Les systèmes réactifs sont élastiques , ce qui signifie qu'ils restent réactifs sous des charges de travail variables. Ceci est réalisé en mettant à l'échelle les composants d'application de manière élastique pour répondre à la demande actuelle.
  4. Les systèmes réactifs sont axés sur les messages, ce qui signifie qu'ils reposent sur le passage de messages asynchrones entre les composants. Cela vous permet de créer un couplage, une isolation et une transparence d'emplacement lâches.

La figure 2 montre comment ces traits se combinent dans un système réactif.

Steven Haines

Caractéristiques d'un système réactif

Les systèmes réactifs sont construits en créant des composants isolés qui communiquent entre eux de manière asynchrone et peuvent évoluer rapidement pour répondre à la charge actuelle. Les composants échouent toujours dans les systèmes réactifs, mais il existe des actions définies à effectuer à la suite de cette défaillance, ce qui maintient le système dans son ensemble fonctionnel et réactif.

Le manifeste réactif est abstrait, mais les applications réactives sont généralement caractérisées par les composants ou techniques suivants:

  • Flux de données : un flux est une séquence d'événements classés dans le temps, tels que les interactions des utilisateurs, les appels de service REST, les messages JMS et les résultats d'une base de données.
  • Asynchrone : les événements de flux de données sont capturés de manière asynchrone et votre code définit ce qu'il faut faire lorsqu'un événement est émis, lorsqu'une erreur se produit et lorsque le flux d'événements est terminé.
  • Non bloquant : lorsque vous traitez des événements, votre code ne doit pas bloquer et effectuer des appels synchrones; au lieu de cela, il doit effectuer des appels asynchrones et répondre lorsque les résultats de ces appels sont renvoyés.
  • Contre-pression : les composants contrôlent le nombre d'événements et la fréquence à laquelle ils sont émis. En termes réactifs, votre composant est appelé l' abonné et les événements sont émis par un éditeur . Ceci est important car l'abonné contrôle la quantité de données qu'il reçoit et ne se surchargera donc pas.
  • Messages d'échec : au lieu de composants lançant des exceptions, les échecs sont envoyés sous forme de messages à une fonction de gestionnaire. Alors que lever des exceptions interrompt le flux, la définition d'une fonction pour gérer les échecs au fur et à mesure qu'ils se produisent ne le fait pas.

L'API Reactive Streams

La nouvelle API Reactive Streams a été créée par des ingénieurs de Netflix, Pivotal, Lightbend, RedHat, Twitter et Oracle, entre autres. Publiée en 2015, l'API Reactive Streams fait désormais partie de Java 9. Elle définit quatre interfaces:

  • Éditeur : émet une séquence d'événements aux abonnés.
  • Abonné : reçoit et traite les événements émis par un éditeur.
  • Abonnement : définit une relation univoque entre un éditeur et un abonné.
  • Processeur : représente une étape de traitement composée à la fois d'un abonné et d'un éditeur et obéit aux contrats des deux.

La figure 3 montre la relation entre un éditeur, un abonné et un abonnement.

Steven Haines

En substance, un abonné crée un abonnement à un éditeur et, lorsque l'éditeur a des données disponibles, il envoie un événement à l'abonné avec un flux d'éléments. Notez que l'abonné gère sa contre-pression dans son abonnement à l'éditeur.

Maintenant que vous en savez un peu plus sur les systèmes réactifs et l'API Reactive Streams, tournons notre attention vers les outils que Spring utilise pour implémenter des systèmes réactifs: Spring WebFlux et la bibliothèque Reactor.

Réacteur de projet

Project Reactor est un framework tiers basé sur la spécification Reactive Streams de Java, qui est utilisé pour créer des applications Web non bloquantes. Project Reactor fournit deux éditeurs très utilisés dans Spring WebFlux:

  • Mono : renvoie 0 ou 1 élément.
  • Flux : renvoie 0 ou plusieurs éléments. Un Flux peut être sans fin, ce qui signifie qu'il peut continuer à émettre des éléments pour toujours, ou il peut renvoyer une séquence d'éléments et envoyer une notification d'achèvement lorsqu'il a renvoyé tous ses éléments.

Les monos et les flux sont conceptuellement similaires aux futurs, mais plus puissants. Lorsque vous invoquez une fonction qui renvoie un mono ou un flux, elle reviendra immédiatement. Les résultats de l'appel de fonction vous seront fournis via le mono ou le flux lorsqu'ils seront disponibles.

Dans Spring WebFlux, vous appellerez des bibliothèques réactives qui renvoient des monos et des flux et vos contrôleurs renverront des monos et des flux. Comme ils reviennent immédiatement, vos contrôleurs abandonneront effectivement leurs threads et permettront à Reactor de gérer les réponses de manière asynchrone. Il est important de noter que ce n'est qu'en utilisant des bibliothèques réactives que vos services WebFlux peuvent rester réactifs. Si vous utilisez des bibliothèques non réactives, telles que les appels JDBC, votre code bloquera et attendra que ces appels se terminent avant de revenir.

Programmation réactive avec MongoDB

Actuellement, il n'y a pas beaucoup de bibliothèques de bases de données réactives, vous vous demandez peut-être s'il est pratique d'écrire des services réactifs. La bonne nouvelle est que MongoDB dispose d'un support réactif et qu'il existe quelques pilotes de base de données réactifs tiers pour MySQL et Postgres. Pour tous les autres cas d'utilisation, WebFlux fournit un mécanisme pour exécuter les appels JDBC de manière réactive, mais en utilisant un pool de threads secondaire qui bloque les appels JDBC.

Démarrez avec Spring WebFlux

Pour notre premier exemple pratique, nous allons créer un service de livre simple qui persiste les livres vers et depuis MongoDB de manière réactive.

Commencez par accéder à la page d'accueil de Spring Initializr, où vous choisirez un projet Maven avec Java et sélectionnez la version la plus récente de Spring Boot (2.0.3 au moment de la rédaction de cet article). Donnez à votre projet un nom de groupe, tel que "com.javaworld.webflux", et un nom d'artefact, tel que "bookservice". Développez le lien Basculer vers la version complète pour afficher la liste complète des dépendances. Sélectionnez les dépendances suivantes pour l'exemple d'application:

  • Web -> Web réactif : cette dépendance inclut Spring WebFlux.
  • NoSQL -> MongoDB réactif : Cette dépendance inclut les pilotes réactifs pour MongoDB.
  • NoSQL -> MongoDB intégré : Cette dépendance nous permet d'exécuter une version intégrée de MongoDB, il n'est donc pas nécessaire d'installer une instance distincte. Ceci est généralement utilisé pour les tests, mais nous l'inclurons dans notre code de version pour éviter d'installer MongoDB.
  • Core -> Lombok : L'utilisation de Lombok est facultative car vous n'en avez pas besoin pour créer une application Spring WebFlux. L'avantage de l' utilisation Lombok projet est qu'il vous permet d'ajouter des annotations aux classes qui généreront automatiquement des accesseurs, constructeurs, hashCode(), equals()et plus encore.

Lorsque vous avez terminé, vous devriez voir quelque chose de similaire à la figure 4.

Steven Haines

Le fait d'appuyer sur Générer un projet déclenchera le téléchargement d'un fichier zip contenant le code source de votre projet. Décompressez le fichier téléchargé et ouvrez-le dans votre IDE préféré. Si vous utilisez IntelliJ, choisissez Fichier , puis Ouvrir , et accédez au répertoire dans lequel le fichier zip téléchargé a été décompressé.

Vous constaterez que Spring Initializr a généré deux fichiers importants:

  1. Un pom.xmlfichier Maven , qui comprend toutes les dépendances nécessaires pour l'application.
  2. BookserviceApplication.java, qui est la classe de démarrage Spring Boot pour l'application.

Le listing 1 montre le contenu du fichier pom.xml généré.