Quarkus, le Saint Graal pour aller par-delà les nuages !

Partons à la découverte de ce nouveau framework Java qui permet de créer des applications Cloud natives, serverless et des microservices rapides et légers et voyons ce qui se cache derrière le slogan somme toute accrocheur « Supersonic Subatomic Java ».

Remontons le temps à la date du 27 mai 2020 pour assister au décollage de la fusée Falcon 9 de SpaceX, depuis le Centre spatial Kennedy de la NASA. A votre avis, quelle joie ont pu ressentir les spectateurs au moment où la Terre a bougé sous leurs pieds ? Quelles sensations de puissance ont eu les deux astronautes à bord de la capsule Crew Dragon au moment du décompte final ? Et si toutes ces sensations étaient perceptibles par un développeur en codant avec Quarkus ?!

Partons à la découverte de ce nouveau framework Java qui permet de créer des applications Cloud natives, serverless et des microservices rapides et légers et voyons ce qui se cache derrière le slogan somme toute accrocheur « Supersonic Subatomic Java ».

 

Qu’est-ce que Quarkus ?

De l’anglais « quark », particule élémentaire, et « us », la chose la plus difficile en informatique, le framework Quarkus est né de l’étude R&D de Red Hat, dont la première version officielle (1.0.0.Final) est sortie en novembre 2019. A la rédaction de cet article, la dernière version en date est la 1.6.1.Final (21 juillet 2020).

Ce framework Open Source full-stack Java natif a été spécialement conçu pour GraalVM et HotSpot, la machine virtuelle de l’OpenJDK et la compilation native. Quarkus permet d’optimiser Java spécifiquement pour les conteneurs afin d’en faire une plateforme efficace pour les environnements sans serveur, Cloud et Kubernetes.

Il apporte les avantages suivants :

  • La joie du développeur
  • Les effets Supersonic et Subatomic
  • L’unification de la programmation impérative et réactive
  • L’apport des meilleures librairies Java et normes déjà connues par les développeurs

 

Pourquoi Quarkus ?

L’adage populaire « Le temps, c’est de l’argent » peut se traduire dans le monde des plateformes de Cloud Computing par « Le CPU, c’est de l’argent ». A moins d’avoir une carte de crédit illimitée, il vaut mieux s’assurer qu’une application à déployer dans un tel environnement soit rapide au démarrage et demande peu de mémoire pour fonctionner.

Contrairement à une architecture monolithique, une plateforme d’applications conteneurisées (par exemple Kubernetes ou Red Hat OpenShift) hébergeant des microservices doit pouvoir rapidement installer de nouvelles instances (ou nodes sous Kubernetes) pour que l’architecture soit agile, scalable et réactive par rapport aux besoins toujours plus rapides du time-to-market.

Même si le langage Java vient de fêter cette année ses 25 ans, on ne peut pas dire qu’il remplit ces critères. L’interprétation du bytecode par la JVM et la nécessité de charger beaucoup de classes au démarrage via l’utilisation de frameworks, tels que Spring ou Hibernate, nécessitent plus d’espace mémoire que les autres écosystèmes plus légers, comme des applications développées en NodeJS ou en Go.

La vérité cachée à propos de Java et les conteneurs

Malgré l’arrivée des compilateurs JIT[1] visant à améliorer les performances des applications Java, il faut attendre l’arrivée de la JVM GraalVM et bénéficier de son compilateur AOT[2] pour déployer des applications Java en exécutable natif dans le Cloud.

Quarkus bénéficie de ces améliorations pour offrir des temps de démarrage spectaculaires (supersonic) ainsi que la réduction du nombre de classes chargées au runtime et donc en optimisant le RSS[3] (subatomic) des applications Java.

Avec Quarkus, une application Java type « REST + CRUD » permet de diminuer son empreinte mémoire par 1,44 (par 8 pour une application native via GraalVM) et d’accélérer son démarrage par 4,67 ; le temps de démarrage passe même de 9,5 secondes à 42 millisecondes si celle-ci est compilée en natif avec GraalVM.

Le mariage entre Quarkus et GraalVM forme le couple gagnant pour créer des applications écrites en Java très performantes dans des environnements Cloud native, microservices et serverless.

 

La joie du développeur

Quarkus a été conçu pour le développeur afin de faciliter le développement de ses applications Java. Le framework inclut des fonctionnalités intégrées pour automatiser les tâches répétitives, permettant d’apporter un gain de productivité aux équipes de développement et directement utilisables quel que soit l’IDE.

Basé sur le plugin Maven io.quarkus:quarkus-maven-plugin (ou équivalent sous Gradle), la simple commande mvn compile quarkus:dev permet de lancer l’application Java en mode développement et de coder en direct sans avoir à relancer/déployer son application. Cette fonctionnalité de « Live Coding » à l’instar de ce qui a déjà été fait auparavant avec le Framework Play ou avec PHP, permet d’adopter le workflow « Write Code → Refresh Browser → Repeat » et offre un gain de productivité au développeur, contrairement au workflow « Write Code → Compile → Deploy → Refresh Brower → Repeat » auquel le développeur est traditionnellement confronté dans la phase de développement des applications Java.

Au niveau de l’outillage, la communauté Quarkus offre une intégration dans la plupart des IDE du marché (EclipseVSCode et IntelliJ) et permet un support d’auto-complétion pour le fichier de configuration application.properties, configuration unifiée dans un seul et même fichier (le format YAML est également supporté) ; ainsi que la possibilité de débugger l’application en mode développement.

La prise en main pour le développeur est grandement facilitée puisque le site web offre également via son « START CODING » un équivalent de Spring Initializr bien connu des développeurs Java utilisant l’écosystème Spring. En quelques clics, un fichier ZIP est téléchargeable et contient la structure du projet entièrement configuré et prêt à l’emploi ; aucune configuration particulière n’est requise.

Il est également possible d’utiliser un artéfact Maven comme le montre la ligne de commande suivante :

> mvn io.quarkus:quarkus-maven-plugin:1.6.1.Final:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=gettingstarted \ -DclassName="org.acme.getting.started.GreetingResource" \ -Dpath="/hello"

Le résultat de la commande ci-dessus génère :

  • La structure Maven du projet
  • Une ressource org.acme.getting.started.GeetingResource exposée sous l’URI /hello
  • Un test unitaire associé
  • Une page accessible à l’adresse http://localhost:8080 après avoir démarré l’application
  • Un exemple de fichiers Dockerfile pour les deux modes native et jvm sous le répertoire src/main/docker
  • Un fichier de configuration de l’application (application.properties sous le répertoire src/main/resources)

 

Il s’agit d’une simple API REST retournant le texte « hello » en réponse à la requête HTTP http://localhost:8080/hello.

Quarkus utilise ici l’extension « RESTEasy JAX-RS » qui est une implémentation de JAX-RS, extension incluse par défaut.

 

Supersonic et Subatomic

Il ne s’agit pas là d’une touche magique mais de l’utilisation d’une partie de GraalVM pour optimiser l’application Java afin de la rendre plus rapide au démarrage (supersonic) et moins gourmande en mémoire (subatomic).

Quarkus permet de s’abstraire des optimisations à réaliser sur le code Java en apportant une configuration quasi automatique via son plugin Maven (ou Gradle) ; il s’appuie notamment sur SubstractVM (composant de GraalVM) et cache ainsi une configuration complexe difficile à maîtriser pour fabriquer une image native.

Cette phase d’optimisation permet de réduire d’environ 99 % le temps de démarrage et d’environ 86 % l’empreinte mémoire des application Java classiques en utilisant la compilation native.

Pour réaliser cette opération, il suffit simplement de lancer la commande Maven suivante :

> mvn package -Pnative[4]

Cette fabrication peut prendre quelques minutes comme le montre la copie d’écran suivante :

 

Unification de la programmation impérative et réactive

Depuis des années, l’architecture Client-Serveur a été le standard pour créer des applications. Ce n’est plus le cas maintenant. De nouvelles architectures ont émergé et impactent la façon dont les applications sont codées, déployées et exécutées.

Le modèle de développement de Quarkus se transforme pour s’adapter au type des applications que vous développez. Basé sur Eclipse MicroProfile et sur la possibilité d’utiliser Vert.x, le framework permet d’unifier la programmation impérative et réactive.

Le développeur Java pourra ainsi dans ses services standards JAX-RS (services exposés par la couche HTTP d’Undertow, le serveur HTTP de JBoss) développer avec une seule API des microservices HTTP, des applications réactives et des applications message-driven (avec une solution comme Kafka) ; seule la réponse change : trois paradigmes, une seule implémentation.

 

L’apport des meilleures librairies Java et normes

Quarkus s’appuie sur un ensemble de librairies utilisées par les développeurs Java, telles que Eclipse MicroProfile et Vert.x. Le système d’injection de dépendances du framework utilise CDI[5], ce qui permet aux développeurs d’utiliser JPA / Hibernate et JAX-RS / RESTEasy, entre autres.

De plus, Quarkus inclut un framework d’extension sur lequel les auteurs peuvent s’appuyer pour l’enrichir. Ce framework d’extension compile également ses éléments dans un binaire natif pour GraalVM.

 

Plus de 90 extensions Quarkus sont disponibles et couvrent la plupart des fonctionnalités requises pour créer des applications Cloud natives :

  • Web : RESTEasy JAX-RS/JSON-B, GraphQL, OpenAPI avec Swagger-UI, RESTEasy Qute…
  • Données : Hibernate, JDBC, MongoDB, Neo4j, Liquibase…
  • Messages asynchrones : Kafka, AMQP…
  • Reactive : Vert.x, Mutiny, Reactive client (DB2, MySQL, PostgreSQL)…
  • Cloud : Health, Fault Tolerance, Kubernetes, OpenShit, Amazon, Minikube…
  • Observabilité : Metrics & OpenTracing par SmallRye
  • Sécurité : OpenID, JSON Web Token, OAuth 2.0, Keycloak, Vault
  • Intégration avec Apache Camel
  • etc.

Une compatibilité est également possible avec l’écosystème Spring (Spring Boot, Spring Security, Spring Web, Spring Data JPA, Spring DI) ; Quarkus offrant des extensions pour convertir le code Spring and code natif[6].

Certaines fonctionnalités sont néanmoins incompatibles ; le support de Spring dans Quarkus ne démarre pas le contexte. Les classes et annotations Spring ne sont utilisées que pour lire les métadonnées et/ou sont utilisées comme types de retour de méthodes dans le code ou de types de paramètres. Ceci implique que l’ajout d’autres librairies Spring n’aura aucun effet. De plus, les classes d’infrastructure Spring (comme org.springframework.beans.factory.config.BeanPostProcessor par exemple) ne seront pas exécutées.

Parmi les nombreuses extensions disponibles, une extension « Hibernate ORM with Panache » simplifie l’accès à la couche de persistance en utilisant le design pattern Active Record[7] sur l’entité JPA. Elle offre des méthodes statiques prédéfinies pour simplifier le requêtage JPQL. Il suffit que l’entité JPA étende la classe abstraite PanacheEntity pour pouvoir bénéficier de méthodes permettant de simplifier le requêtage sur la base de données. Par exemple, la méthode statique findAlive filtre la liste des personnes en utilisant la méthode prédéfinie list pour ne retourner que les personnes ayant l’attribut status égale à Status.Alive.

A noter que l’entité Person ne possède pas d’identifiant car celui-ci est déjà présent dans la classe abstraite PanacheEntity.

 

Quarkus est-il l’avenir de ava ?

Même si des architectures monolithiques resteront toujours d’actualité, les entreprises ont aujourd’hui une multitude de choix pour créer des applications basées sur des solutions de Cloud Computing clés en main avec Microsoft Azure, Amazon Web Services ou Google Cloud Platform (pour ne citer que les principaux leaders du marché) :  environnements Cloud native, microservices, serverless.

Ce qui est sûr, c’est que la communauté Java rattrape son retard pour aller dans cette direction aux vues de nombreux projets qui tentent d’accélérer les temps de démarrage et de diminuer le RSS ; on peut citer notamment :

  • Le projet expérimental Spring GraalVM Native dont le but est de founir un moyen de fabriquer des images natives pour des applications Spring Boot
  • Le projet TornadoVM dans le but d’accélérer les applications Java à l’aide des processeurs type CPUs, GPUs et FPGAs[8]
  • Le projet à l’étude nommé Leyden en collaboration avec GraalVM visant à améliorer les performances des applications Java

Alors, Quarkus est-il l’avenir de Java ? Seul le temps nous le dira. Pour l’instant la compilation native reste une technologie nouvelle et non-maîtrisée par les développeurs ; cela demande un degré de complexité supérieur pour bien paramétrer GraalVM. Pour exemples :

  • L’utilisation de l’annotation Quarkus @RegisterForReflection car GraalVM analyse l’arbre des appels et supprime toutes les classes / méthodes / attributs qui ne sont pas utilisés directement
  • Les fichiers de ressources utilisés dans l’application Java doivent être explicitement déclarés dans un fichier JSON pour que ceux-ci soient pris en compte lors de la compilation native (par exemple la clé quarkus.native.additional-build-args=-H:ResourceConfigurationFiles=resources.config.json est à ajouter dans le fichier application.properties)

Ce qui est sûr, c’est que ce framework a un temps d’avance par rapport à ses concurrents ; outillage, « Live Coding », fabrication des images en retirant au développeur la complexité de paramétrage de GraalVM, création des images Docker : le tout avec l’unique plugin quarkus-maven-plugin.

Des entreprises comme Sedona, Lufthansa Technik ou Vodafone Greece font déjà confiance à Quarkus pour construire leurs applications (voir https://quarkus.io/blog/tag/user-story/).

Alors, convaincu ? Laissez-vous surprendre par la puissance de Quarkus, c’est une des façons de rendre un développeur Java heureux ! Rendez-vous sur https://code.quarkus.io pour un nouveau départ.

PS : La dernière version de Quarkus 1.6.1.Final requiert au minimum la JDK 11 (il supporte Java 14) et GraalVM en version 20.1.0 pour une compilation native.

″And the man in the rain picked up his bag of secrets, and journeyed up the mountainside, far above the clouds, and nothing was ever heard from him again, except for the sound of Tubular Bells Quarkus.″ – Mike Oldfield – Tubular Bells III

 

 

[1]     Un compilateur JIT pour « Just-In-Time » (ou compilation à la volée en français) aide à améliorer la performance des programmes Java en compilant le bytecode en un code machine natif au moment de l’exécution (runtime) ; le compilateur HotSpot JIT de Sun a été activé par défaut en 1996 dans la version Java 1.3

[2]     Un compilateur AOT pour «Ahead Of Time » (ou compilation anticipée en français) est une compilation qui traduit un langage évolué (le bytecode) et langage machine (exécutable natif) avant l’exécution d’un programme Java contrairement à une compilation JIT qui se fait lors de l’exécution du programme.

[3]     Le RSS (Resident Set Size) correspond à la taille totale de la mémoire physique (RAM) utilisée par le process d’une application sur le système

[4]     Ou la commande équivalente ./gradlew buildNative pour le plugin Gradle

[5]     CDI (Contexts and Dependency Injection) est un framework standard pour l’injection de dépendances introduit dans Java EE 6 et permet par exemple d’utiliser l’annotation @Inject

[6]     Dans la limite du possible, certaines fonctionnalités étant incompatibles avec Quarkus, les annotations Spring supportées seront converties (par exemple @Autowired → @Inject, @Service → @Singleton, @Secured → @RolesAllowed, etc …)

[7]     Patron de conception identifié par Martin Fowler permettant d’encapsuler l’accès à la donnée et la logique métier dans un seul et même objet.

[8]     Exploitation via l’accélération matérielle avec l’utilisation des microprocesseurs (CPUs) processeurs graphiques (GPUs) et de circuits intégrés composés de réseaux de cellules programmables (FPGAs)