Une introduction à Vert.x, le framework Java le plus rapide aujourd'hui

Si vous avez récemment recherché sur Google le «meilleur framework Web», vous êtes peut-être tombé sur les benchmarks Techempower où plus de trois cents frameworks sont classés. Là, vous avez peut-être remarqué que Vert.x est l'un des premiers classés, sinon le premier à certains égards.

Alors parlons-en.

Vert.x est un framework Web polyglotte qui partage des fonctionnalités communes parmi ses langages pris en charge Java, Kotlin, Scala, Ruby et Javascript. Quelle que soit la langue, Vert.x fonctionne sur la machine virtuelle Java (JVM). Étant modulaire et léger, il est orienté vers le développement de microservices.

Les benchmarks Techempower mesurent les performances de mise à jour, de récupération et de livraison de données à partir d'une base de données. Plus il y a de demandes servies par seconde, mieux c'est. Dans un tel scénario d'E / S où peu de calcul est impliqué, tout cadre non bloquant aurait un avantage. Ces dernières années, un tel paradigme est quasiment inséparable de Node.js qui l'a popularisé avec sa boucle d'événement monothread.

Vert.x, comme Node, exploite une seule boucle d'événements. Mais Vert.x profite également de la JVM. Alors que Node fonctionne sur un seul cœur, Vert.x gère un pool de threads dont la taille peut correspondre au nombre de cœurs disponibles. Avec une meilleure prise en charge de la concurrence, Vert.x convient non seulement aux E / S, mais également aux processus gourmands en ressources processeur qui nécessitent un calcul parallèle.

Les boucles d'événements, cependant, représentent la moitié de l'histoire. L'autre moitié a peu à voir avec Vert.x.

Pour se connecter à une base de données, un client nécessite un pilote de connecteur. Dans le domaine Java, le pilote le plus courant pour Sql est JDBC. Le problème est que ce pilote se bloque. Et ça bloque au niveau du socket. Un thread y restera toujours bloqué jusqu'à ce qu'il revienne avec une réponse.

Inutile de dire que le pilote a été un goulot d'étranglement dans la réalisation d'une application entièrement non bloquante. Heureusement, il y a eu des progrès (bien que non officiels) sur un pilote asynchrone avec plusieurs fourches actives, parmi lesquelles:

  • //github.com/jasync-sql/jasync-sql (pour Postgres et MySql)
  • //github.com/reactiverse/reactive-pg-client (Postgres)

La règle d'or

Vert.x est assez simple à utiliser, et un serveur http peut être mis en place avec quelques lignes de code.

La méthode requestHandler est l'endroit où la boucle d'événements délivre l'événement de demande. Comme Vert.x est sans opinion, sa gestion est libre. Mais gardez à l'esprit la seule règle importante du thread non bloquant: ne le bloquez pas.

Lorsque nous travaillons avec la concurrence, nous pouvons tirer parti de tant d'options disponibles aujourd'hui telles que Promise, Future, Rx, ainsi que la manière idiomatique de Vert.x. Mais à mesure que la complexité d'une application augmente, la seule fonctionnalité asynchrone ne suffit pas. Nous avons également besoin de la facilité de coordination et de chaînage des appels tout en évitant l'enfer des rappels, ainsi que de passer toute erreur avec grâce.

Scala Future remplit toutes les conditions ci-dessus avec l'avantage supplémentaire d'être basé sur des principes de programmation fonctionnelle. Bien que cet article n'explore pas Scala Future en profondeur, nous pouvons l'essayer avec une application simple. Disons que l'application est un service API pour trouver un utilisateur en fonction de son identifiant:

Il y a trois opérations impliquées: vérifier le paramètre de demande, vérifier si l'ID est valide et récupérer les données. Nous envelopperons chacune de ces opérations dans un Futur et coordonnerons l'exécution dans une structure «pour la compréhension».

  • La première étape consiste à faire correspondre la demande avec un service. Scala a une puissante fonction de correspondance de modèles que nous pouvons utiliser à cette fin. Ici, nous interceptons toute mention de «/ user» et la transmettons à notre service.
  • Vient ensuite le cœur de ce service où nos futurs sont organisés dans un ordre séquentiel de compréhension. La première future vérification des paramètres f1 wraps. Nous voulons spécifiquement récupérer l'id de la requête get et le convertir en int. (Scala ne nécessite pas de retour explicite si la valeur de retour est la dernière ligne de la méthode.) Comme vous le voyez, cette opération pourrait potentiellement lever une exception car id peut ne pas être un int ou même pas disponible, mais c'est correct pour le moment .
  • Le second futur f2 vérifie la validité de id. Nous bloquons tout identifiant inférieur à 100 en appelant explicitement Future.failed avec notre propre CustomException. Sinon, nous passons un Future vide sous la forme de Future.unit comme validation réussie.
  • Le dernier futur f3 récupère l'utilisateur avec l'id fourni par f1. Comme il ne s'agit que d'un exemple, nous ne nous connectons pas vraiment à une base de données. Nous retournons juste une fausse chaîne.
  • map exécute la disposition qui produit les données utilisateur de f3 puis les imprime dans la réponse.
  • Maintenant, si dans n'importe quelle partie de la séquence une erreur se produit, un Throwable est passé à récupérer . Ici, nous pouvons faire correspondre son type à une stratégie de récupération appropriée. En regardant en arrière dans notre code, nous avons anticipé plusieurs échecs potentiels tels que l'id manquant, ou l'id qui n'était pas entier ou non valide qui lèverait des exceptions spécifiques. Nous gérons chacun d'eux dans handleException en transmettant un message d'erreur au client.

Cet agencement fournit non seulement un flux asynchrone du début à la fin, mais également une approche propre de la gestion des erreurs. Et comme il est rationalisé entre les gestionnaires, nous pouvons nous concentrer sur les choses qui comptent, comme les requêtes de base de données.

Verticles, Event Bus et autres pièges

Vert.x propose également un modèle de concurrence appelé verticle qui ressemble au système Actor. (Si vous souhaitez en savoir plus, consultez mon guide Akka Actor.) Verticle isole son état et son comportement pour fournir un environnement thread-safe. Le seul moyen de communiquer avec lui est via un bus d'événements.

Cependant, le bus d'événements Vert.x exige que ses messages soient String ou JSON. Cela rend difficile la transmission d'objets non POJO arbitraires. Et dans un système haute performance, gérer la conversion JSON n'est pas souhaitable car cela impose des coûts de calcul. Si vous développez des applications IO, vous feriez peut-être mieux de ne pas utiliser de bus vertical ou d'événements, car ces applications ont peu besoin de l'état local.

Travailler avec certains composants Vert.x peut également être assez difficile. Vous pouvez trouver un manque de documentation, un comportement inattendu et même un échec de fonctionnement. Vert.x pourrait souffrir de sa propre ambition, car le développement de nouveaux composants nécessiterait un portage dans de nombreuses langues. C'est une entreprise difficile. Pour cette raison, s'en tenir au noyau serait le meilleur.

Si vous développez une API publique, alors vertx-core devrait suffire. S'il s'agit d'une application Web, vous pouvez ajouter vertx-web qui fournit la gestion des paramètres http et l'authentification JWT / Session. Ce sont ces deux-là qui ont de toute façon dominé les benchmarks. Il y a une certaine diminution des performances dans certains tests d'utilisation de vertx-web, mais comme cela semble provenir de l'optimisation, cela pourrait être corrigé dans les versions suivantes.