Devoxxfr – GraalVM et Quarkus changent la donne

Cette session va essayer d’expliquer ce qu’est Quarkus et comment ça marche. A la base, Quarkus, c’est un framework pour écrire des applications Java …​ mais ça, on le savait déja. L’objectif, c’est de faire du micro-service, voire même du serverless.

Emmanuel commence par une démo …​ classique puisqu’il s’agit du tutorial Quarkus. Et Il nous montre aussi que quand l’IDE compile la classe, Quarkus recharge l’application (à la première requête). C’est très rapide.

Emmanuel va donc continuer en développant une application de todo. Dans Quarkus, les dépendances sont disponibles sous la forme d’extensions, gérées par le plugin maven de Quarkus. Du coup, rendre l’application compatible Hibernate, c’est facile et rapide. Le petit point négatif, c’est qu’il faut arrêter le mode dev pour ajouter une dépendance. Et ça, j’en ai discuté après avec Clément Escoffier, parce que du temps de Wisdom, ça marchait 🙂 Apparemment, je me prépare une PR pour l’automne …

Pour Hibernate, Emmanuel utilise Hibernate with Panache, qui est une version spécifique d’Hibernate ne couvrant que les cas communs (ce qui correspond néanmoins à l’immense majorité des applications). L’entité écrite n’a pas de getters/setters, pour une raison que je n’ai pas bien compris …​ Comme hibernate-panache simplifie les choses, les entités sont écrites « à la sauce ActiveRecord », c’est-à-dire avec un certain nombre de méthodes préexistantes (comme par exemple listAll()).

Pendant qu’Emmanuel arrête sa démo, il nous parle des tests, qui utilisent une annotation @QuarkusTest qui démarre l’application, effectue le test et injecte le test dans l’application.

Pour revenir au pourquoi …​ Avant, on faisait des monolithes, qu’on a commencé à couper en micro-services ou en fonction. Du coup, on a maintenant moins de temps et de développeurs pour travailler sur le composant. Les fonctions ajoutent une complexité supplémentaire : les fonctions doivent pouvoir démarrer, scaler et s’arrêter de façon très rapide.

Pourquoi ajouter cette complexité ? Pour gagner en agilité, en scalabilité et en réactivité métier.

Malheureusement, ça n’est pas compatible avec Java : Java démarre plus lentement à cause du nombre de classes, le bytecode, le JIT. Et en plus il y a un surcoût mémoire dû à la représentation des classes et de leurs métadonnées.

Évidement, ça rend Java peu intéressant face à Node ou Go.

Donc, RedHat a créé Quarkus. Qu’est-ce que ça apporte ?

D’abord le plaisir de développement

  • Quarkus utilise des standards facile à reprendre
  • La configuration est unifiée dans application.properties
  • La configuration n’est souvent pas nécessaire, et il y a du live-reload

Ensuite la consommation de ressources, puisque l’application démarrée avec GraalVM prend 23 Mo contre 218 Mo pour un déploiement équivalent avec par exemple Spring Boot.

Quarkus unifie également les approches réactives et bloquantes, en permettant aux deux de coexister dans la même application.

On parle de GraalVM, mais qu’est-ce que c’est ?

Plusieurs choses, évidement.

  • Un interpréteur (Trufle) pour la programmation polyglote)
  • Le compilateur (utilisable avec Hotspot)
  • SubstrateVM qui transforme une application Java en binaire système

SubstrateVM élimine l’ensemble du code mort (le binaire est donc allégé) grâce à une compilation native en « closed world » (c’est-à-dire en imaginant avoir tout le code). Ca a quelques inconvénients …

Il n’y a pas de ClassLoader dynamique.

L’introspection est possible …​ si la classe est référencée (sinon la classe est supprimée par SubstrateVM). Evidement, de la même manière, les proxies ne marchent pas. Les lambdas sont supportées. Les initialiseurs statiques sont tous appelés au démarrage.

Donc effectivement, beaucoup d’éléments sont déplacés au moment du build (ce qui explique la lenteur de la compilation native). Qu’est-ce qui est fait ?

  • Lire les fichiers de configuration
  • Le scan de CLASSPATH (pour les annotations, les getters et autres métadonnées)
  • On construit le métamodèle
  • On prépare l’introspection

Quarkus fournit pour tous ces éléments de quoi aider les frameworks, parce que ça rend le démarrage plus rapide. De la même manière, l’intelligence qui a lieu à ce moment-là n’est plus nécessaire. Donc on peut supprimer les classes et ça allège l’application. Et enfin, Quarkus alimente les fichiers nécessaires à GraalVM pour ,optimiser l’application.

Kotlin est également supporté. Il y a également des explorations qui ont été faites pour développer des applications en Javascript avec Quarkus.

Conclusion

Je ne l’ai pas encore assez écrit, mais je suis fan de Quarkus : l’idée est incroyablement géniale, et la réalisation extrêmement léchée. Franchement, pour moi, c’est une belle façon de remettre Java dans la course, précisément parce que le couple Quarkus/GraalVM permet de remettre Java dans le contexte conteneurisé ou ses avantages initiaux ne tenaient plus. Pour le dire autrement, essayez Quarkus, vous verrez, c’est génial.

Publicités

Apprendre Java, c’est pas si facile

Ces deux dernières semaines, j’ai formé une petite dizaine de personnes à Java (avec un peu de git et de maven).

Quand j’ai appris Java … il y a maintenant 20 ans … c’était un langage facile.

Et dans ces vingt dernières années, un certain nombre d’éléments de complexité ont été ajoutés

  • Les diverses nouvelles instructions (le try-with-resources a par exemple fait pas mal de sens)
  • L’autoboxing (qui n’a pas semblé dramatique)
  • Les types génériques (pas facile à comprendre pour un débutant)
  • Les streams Java 8 (vraiment durs à comprendre)

En bonus, il y a des éléments qui sont d’authentiques richesses de la plateforme, mais qui ont compliqué la vie des stagiaires (et typiquement, c’est le cas des collections).

Les stagiaires sont donc passé par des sales moments, mais dans l’ensemble, ils ont compris la plupart de ces concepts. Surtout que je leur ai montré certains usages spectaculaires de la plupart des features.

J’ai aussi noté tout l’intérêt du travail en groupe. En particulier, le mob programming est un super outil dans ce cas. Et j’ai également découvert de chouettes implémentations de FizzBuzz, des nombres romains et autres.

En particulier, je me suis fait un kata résolu avec ce super stream

Evidement, c’est le genre de truc qui effraie les stagiaires. Mais je pense qu’en un sens ça leur a montré la diversité des styles de code en Java …

En tout cas, j’ai appris beaucoup, malgré la fatigue terrible du truc. Parce que bon, formateur, c’est loin d’être un métier facile.

Vertx, c’est vachement bien !

J’ai commencé récemment un projet lisant des messages d’AciveMQ pour les écrire dans Kafka. J’aurais pu utiliser Kafka Connect, mais les condtions de licence sont peu favorables au type de déploiement envisagé. Et comme le client m’a laissé libre de la technologie, j’ai choisi de créer une petite application Vertx (parce que ça faisait longtemps que je voulais tester). En fait, j’ai déja utilisé Vertx, mais indirectement, à travers … Wisdom Framework (que j’ai beaucoup aimé utiliser). Donc Vertx c’est bien, parce que c’est simple, rapide asynchrone, tout ça.

Et je voudrais vraiment que vous reteniez ça, parce que je vais rentrer dans les petits détails qui grattent.

D’abord, vertx, comme Wisdom, justement, a un chouette plugin maven pour faire du rechargement à chaud. C’est super efficace pour l’expérience du développeur. Mais, à la différence de Vertx, le plugin ne détecte que les changements dans le code, et pas dans le pom.xml. Il y a un ticket dans GitHub à ce sujet … mais sans avancement visible (et c’est d’autant plus dommage que Clément Escoffier a bossé sur le plugin Wisdom pour maven). C’est un peu gênant, mais moins que l’autre problème …

L’autre problème, c’est celui de la ligne de commande de Windows et sa limite à 8192 caractères, comme je l’ai indiqué sur Github. Le meilleur contournement que j’ai trouvé a été d’utiliser subst (évidement à travers une interface graphique) pour mapper mon repository maven sur le disque virtuel M:.

A part ça, le build se passe assez bien … Du moment qu’on a bien codé.

Et là, les surprises sont nombreuses.

D’abord, il m’a fallu un peu de temps avant de comprendre que, pour utiliser le plugin Maven dans de bonnes conditions, il ne faut pas créer un main(...), mais plutôt un « MainVerticle » dans lequel on met le code d’initialisation avant de mettre le code applicatif dans d’autres verticles que ce MainVerticle charge. C’est un mode de déploiement comme un autre … sauf quand j’ai voulu ajouter les métriques avec micrometer. Parce que micrometer demande à manipuler les VertxOptions. Et ça, ça se fait dans un Launcher comme celui-ci

Comme vous le voyez, l’implémentation n’est pas forcément immédiate, puisqu’il faut surcharger à la fois le main et la méthode de construction des options.

Vertx, c’est bien, mais il n’y a pas d’injection de dépendances … sauf avec vertx-weld. Et j’aime bien CDI, le modèle est bon, il y a des possibilités intéressantes d’événements et de cycle de vie … sauf que parfois, ça coince !

Si vous suivez le fil, vous verrez que Clément me propose une solution. Merci Clément ! Malheureusement, la bonne solution n’est pas celle-là. Sans vraiment rentrer dans les détails, WeldWebVerticle déclare une extension Weld : RouteExtension, laquelle fournit l’injection de l’objet Vertx. Je me suis donc dit qu’il était possible d’étendre WeldWebVerticle pour permettre de rendre le routeur injectable.

Il y a deux problèmes avec ça.

  1. La méthode WeldWebVerticle#createRouter() retourne toujours une nouvelle instance de routeur, ce qui met un sacré bazar.
  2. Pour rendre le routeur injectable, il faut créer un Bean (au sens Weld) produisant cet objet.

Pour le premier point, ça n’est pas compliqué, il suffit de surcharger createRouter pour retourner toujours la même instance. Par contre, pour le second … je ne voulais pas réimplémenter toute l’interface Bean, j’ai donc cherché à réutiliser une implémentation existante, comme VertxBean par exemple. Malheureusement, VertxBean est une classe privée, j’ai donc dû la recopier … et ça, je n’aime pas trop.

Une fois que ces petits soucis de style ont été résolus, développer avec Vertx est vraiment une expérience hautement recommandable. En fait, j’ai l’air de râler, mais j’ai passé un bon moment à développer avec Vertx, et j’espère que ça n’est pas fini !

Introduction à JHipster au chtijug

Julien Dubois vient donc nous présenter le framework qu’il a créé …​ JHipster. Spring, c’est un gros paquet d’ingrédients. Spring Boot, c’est la même chose bien packagée. Et JHipster, c’est ce package encore décoré. Autrement dit, c’est un générateur d’application full stack avec Spring Boot en back-end, Angular et React en front-end (mais Vuejs arrive bientôt), et préconfiguré pour être facilement déployable.

A noter que JHipster est 100% open-source sous licence Apache (la meilleure pour le commerce, quand même).

C’est un projet qui réussit : 22 développeurs dans la core team, 500 contributeurs, des conférences, des étoiles GitHub, …​

Pourquoi ?

JHipster fait gagner du temps au démarrage (avec des études qui montrent un gain de 6 semaines au démarrage), au build (comme le projet est très utilisé, le code généré est stable et fiable), et au run (le monitoring est déja prévu).

Stack technique

front-end

JHipster propose une stack complète, avec des front bien connus. Mais attention : tous les widgets de l’univers ne sont pas supportés. Parce que ça fait trop de code d’une part, mais aussi parce que ce sont des technologies qui évoluent très rapidement.

back-end

Evidement, le back est intégralement Spring .. il y a toutefois quelques choix qui sont faits (par exemple, JHipster utilise toujours DropWizard Metrics, parce que la communauté n’est pas fan des nouvelles métriques Spring).

Outillage

Les tests de base sont déja préparés. En revanche, les tests Gatling ou Protactor sont optionnels …​ mais ajoutables facilement.

Par ailleurs, JHipster suppose que vous utilisez Docker …​ parce que c’est pratique et c’est quasiment un standard. Cependant, si vous voulez utilisez MySQL ou Elastic, il faudra en passer par là.

Le déploiement est préparé pour heroku (beaucoup utilisé parce que gratuit), Kubernetes, OpenShift, …​

Architecture

JHipster permet de générer une architecture microservice, en créant un module par service de back-end et un ou plusieurs modules de front-end.

Il peut aussi utiliser une service registry (celle de Netflix ou de Consul sont intégrées) ou une API gateway.

L’objectif étant d’avoir la même expérience développeur que pour un monolithe. Ca paraît séduisant sur le papier …​

Et enfin, il est possible de créer des microservices réactifs utilisant Spring Boot 2 …​ et les composants Spring réactifs, évidement.

Toutefois, sur le sujet des microservices, je me dois d’avouer qu’à mon ens on est déja là dans une démarche dépassée : aujourd’hui, le marché semble converger vers les service meshs (Istio, Consul Connect, ou Conduit). Et franchement, continuer à implémenter la couche de service dans l’application … c’est lourd, c’est peu efficace, et ça rajoute de la complexité au mauvais endroit.

Démo

Et c’est parti pour la démo à la cool !

Installation

On peut installer JHipster avec npm (wat ?), Homebrew, Chocolatey, …​

Mais on peut aussi, comme pour Spring boot (ou Wisdom Framework), créer le projet depuis le site web de JHipster. Donc on se connecte, et grâce à l’intégration GitHub/GitLab, le projet est directement créé dans le gestionnaire de source (comme avec Jenkins-X).

Les options de création de projet sont assez nombreuses (monitoring, authentification, base de données, …​). C’est l’un des intérêts de JHipster : gérer des sujets auxquels Spring Boot ne s’intéresse pas. L’impact évident, c’est que le démarrage est plus lent que Spring Boot …​ qui n’est déja pas fameux sur ce sujet. Parce que JHipster démarre un EHCache, un hibernate, …​

En dehors de ça, JHipster peut gérer l’accès à ElasticSearch, ou Kafka. Dans le cas d’Elastic, ça a un impact sur le front et sur le back (parce que JHipster génère les outils d’accès, et l’interface de recherche). Qui plus est, le docker-compose.yaml sera également généré pour le développement avec Elastic.

Et lorsqu’on clique sur « generate on GitHub », le projet est automatiquement généré avec les bons *ignore.

Ouverture du projet

Donc le projet a été généré avec un pom.xml pour maven, et un package.xml pour générer le front-end. Et au lancement, on démarre node et maven, le front et le back se lance (pour le front, après avoir copié 1/2 Go de dépendances Node). Ca me trouble un chouaï, parce que je ne vois pas pourquoi il faut lancer deux outils de build différents … pour produire les deux morceaux de la même application.

Spring-boot et webpack sont configurés pour faire du rechargement à chaud, ce qui fait qu’il n’y a pas besoin de relancer les outils de build. C’est quelque chose qu’on a aussi dans Spring-Boot, dans Wisdom Framework – mais bien mieux (désolé, mais c’est vrai) ou même en Rust avec cargo watch.

L’application est générée avec de l’authentification, ce qui est cool, mais aussi tout un tas d’écrans d’administration. On peut voir les métriques, les niveaux de logs (et les changer), …​ C’est franchement pratique.

Il y a également un browser-sync, qui permet d’avoir plusieurs clients synchronisés (c’est typiquement pratique pour le développement web avec des écrans réactifs).

Et encore la configuration Docker …​ avec le fichier de configuration permettant l’accès à la base de données.

Et beaucoup de configuration Java, pour optimiser les performances de Jackson, par exemple, ou pour corriger des implémentations pas optimales, ou pour lier le front-end et le back-end. Une librairie équivalente existe évidement côté Javascript, encore une fois pour faciliter la vie du développeur.

Et enfin les bases d’une application : un utilisateur, ses droits, les repository Spring Data, les contrôleurs web permettant d’y accéder, et même les messages internationalisés.

Ecrire du code

JHipster fournit des sous-générateurs. Donc pour créer une nouvelle entité, il suffit de taper jhipster entity Foo par exemple.

JHipster Studio

Avec ce studio, on peut décrire facilement des entités dans un DSL …​ qui ressemble furieusement à un descripteur UML. Ce studio est utilisable dans une interface web, ou via un plugin Eclipse (cool). Et le modèle peut être appliqué sur une application préexistante. L’avantage du studio web, c’est qu’il génère une pull request automatiquement, ce qui est cool. Ce qui l’est moins, c’est que 110 fichiers ont été générés pour 4 entités. 110 fichiers ! C’est pas un peu beaucoup ?

Si, mais il y a quand même un front-end, un back-end, et toute la connexion entre les deux. C’est malgré tout impressionnant.

Même si ça ne marche pas.

Méta-modèle

Julien rencontre à ce moment-là une difficulté qui l’oblige à tout régénérer …​. y compris le code Java. Pour ça, JHipster maintient un méta-modèle avec lequel il génère le code Java et le code Javascript.

Ca ne marche pas mieux après, mais c’est néanmoins impressionnant.

Surtout quand je me rends compte qu’en fait, JHipster est un outil de génération à base de modèle UML. Modèles qui sont bien cachés, c’est vrai, mais qui se révèlent bien pratiques.

Différence dev-prod

Pour aller plus loin, Julien nous montre que l’application, quand elle se lance en mode production, démarre plus lentement, parce que les comportements sont différents :

  • Le code Angular est minifié
  • Les stratégies de cache côté serveur sont plus aggressives
  • Les descripteurs Swagger peuvent (ou pas) être générés

Tests

Sur cette application, 160 tests sont générés pour le back et 152 pour le front. Et c’est là qu’on voit tout ce que cache ce générateur …​. Il y a 5 entités. Et ça mérite 300 tests ? J’imagine que oui, bien sûr.

Mise à jour

La ligne de commande contient une commande jhipster update qui permet de mettre à jour le projet par rapport à une version suivante, via un merge git. C’est conceptuellement vraiment, mais alors vraiment balaize. Et ça facilite évidement énormément les mises à jour de version.

Mise en prod

Pour finir, il existe des sous-générateurs permettant de déployer l’application dans les infrastructures de prod. Julien prend donc l’exemple de jhipster heroku …​ qui déploie chez heroku (dingue).

Et les différents cloudeurs ont produit des optimisations spécifiques. Par exemple, Google App Engine déploie le front-end sur son CDN, et les requêtes qui arrivent sur votre « serveur » sont celles qui concernent le back uniquement.

Conclusion

Julien vend particulièrement bien son outil. Et honnêtement, il a raison : JHipster est bien fichu, et fournit un certain nombre de services spectaculairement pratiques. L’un des plus importants, mais des moins visibles, est la mise en place d’une approche MDA pragmatique et efficace. En revanche, ayant utilisé pendant un certain temps Wisdom Framework, je trouve le choix de se baser sur Spring pour construire un générateur d’applications magnifié me paraît …​ discutable : je trouve la quantité d’éléments générés hallucinante, pour un résultat qui est raisonnablement joli, mais pas non plus fascinant (typiquement, les démos de Julien n’ont montré que Twitter Bootstrap (aucun des éléments visuels ne nécessitait Angular ou React). Malheureusement, je pense qu’il est de moins en moins pertinent de « gâcher » du CPU en fournissant un échafaudage de projet – aussi poussé soit-il. En effet, on voit maintenant émerger des approches – type « Function as a service » – qui interdisent totalement ce genre d’outillage.

Alors certes ça n’est pas la cible.

Mais quand même. Quelle application déployer avec JHipster ? Un outil « léger » non critique ? Quel genre d’outil peut encore avoir besoin de la « puissance » de la JVM, en étant toutefois assez simple pour être développable en PHP ?

Autrement dit, simplifier Spring, pour moi, est une « solution » qui ne me paraît pas, malgré tous les exemples de mise en oeuvre présentés – avec brio, je dois dire – par Julien, être applicable à un cas « utile ». Quelque part, c’est avant tout un outil de commodisation du développement, applicable dans les cas où le logiciel ne doit pas être un obstacle à une tâche, mais ne doit pas non plus être un facteur de succès.

Devoxxfr – java.lang.Invoke

Finir Devoxx avec Rémi, c’est toujours cool.

On va s’intéresser aux loggers quand ils ne loggent pas. Et plus précisément quel est le coût d’exécution d’un logger qui ne logge pas ? Avec JMH, on a le résultat : ça prend 1 ns avec log4j2.

Et avec une lambda ? Ou un logger.isDebugEnabled() ? Ca prend autant de temps.

Normalement, le logger.isDebugEnabled() est un ensemble d’appels à des valeurs constantes. Et ça prend du temps parce que …​

La JVM ne croit pas que les champs final sont des constantes.

A cause de la …​ (de)serialization !!! Autrement dit, comme la JVM peut (dé)sérialiser tous les champs final ne peut pas inliner leurs appels.

java.lang.invoke

Avec cette API, on peut appeler du code dynamiquement d’une façon plus efficace que l’introspection pour plusieurs raisons :

  • Mise en cache de la sécurité
  • Pas de boxing

L’API est plus riche pour implémenter des langages. Elle est utilisée pour les lambdas, et pour la concaténation de chaînes de caractères en Java9.

Avec cet API on crée un MethodHandle qui est une espèce de pointeur de fonction. Il permet aussi de faire de l’invocation partielle. Et l’élément sur lequel il sera bindé sera considéré par le JIT comme une constante. En plus, il permet d’ajout des arguments qui ne seront pas passés à la méthode. Et de créer des MethodHandle vides ou de placer des gardes sur le MethodHandle.

Avec tout ça, Rémi recréée son Logger en utilisant exclusivement des MethodHandle. Le code est verbeux, mais on sent bien qu’il est particulièrement optimisé.

Bon, ça marche pas aussi bien que prévu. Allons donc voir le code source …​ de la JVM !

Le seul cas qui soit exploitable, c’est celui des classes anonymes (au sens de la JVM, pas du langage). Et ces classes anonymes au sens du langage sont les lambdas.

Ca marche bien.

Allons voir plus loin.

Avec une simple boucle et un logger.debug(…​) mais cette fois-ci avec log4j, on se retrouve avec des temps d’exécution différents là où il ne devraient pas être différents. Le tout à cause d’un volatile dans log4j. Ca amène Rémi à nous parler de SwitchPoint.

Et tout ce code est dans le projet Beautiful Logger. Qui lui est capable de ne pas logger sans consommer de temps CPU. En utilisant les mêmes techniques, Rémi a créé un projet de classes exotiques.

Mon avis

Je savais que ça allait dépoter, et je n’ai pas été déçu. Rémi nous en a mis plein la tronche avec des phrases qui se retrouveront sur son compte Twitter (ça n’est pas vraiment lui qui twitte).

Devoxxfr – Lazy Java

Mario vient nous parler de la progammation lazy, fortement utilisée en programmation fonctionnelle, mais rare en style impératif. Qu’est-ce que c’est ? C’est une stratégie d’évaluation qui fait en sorte que la valeur ne soit calculée que quand elle est utilisée. En programmation fonctionnelle, une fonction stricte évalue ses valeurs immédiatement, quand une fonction lazy évalue ses valeurs uniquement quand elles sont nécessaires.

Et donc, globalement, Java est un langage strict …​ A quelques exceptions près (les opérateurs booléeans, l’opérateur ternaire, certaines structures de contrôle, et les streams). Mais on peut rendre Java beaucoup plus lazy. Ca rend par exemple l’opérateur ternaire difficile à remplacer par une fonction …​ sauf en utilisant des lambdas (en l’occurence les Supplier<T>). C’est utilisé dans les apis de log modernes, par exemple (voir dans log4j 2).

Et franchement, c’est une sacrément bonne méthode d’optimisation : rien ne va plus vite que de ne pas appeler le code !

Et les streams utilisent ça : on peut créer un stream d’entiers infini : IntStream.iterate(1, i → i+1). Et du coup, un stream, ça n’est pas une structure de données, mais la spécification de données qu’on peut obtenir. Et grâce à ça, l’aspect lazy du code des streams permet la séparation des responsabilités. Et pour Mario, c’est cet aspect, plus que la compacité du code, qui rend les streams utile.

Et là, Mario nous sort un exemple de calcul de nombre premiers classique d’abord, puis récursif avec un head et un tail sur la liste des nombres. Cet exemple ne marche pas …​ Parce qu’un stream ne peut être terminé qu’une fois. Et que sa récursion n’est pas lazy, du coup, pouf, une boucle infinie ! Alors qu’en Scala, il y a une lazy concatenation (l’opérateur #::) qui aurait permis au premier exemple de marcher.

Donc, Mario recrée une liste lazy à base de head/tail et de Supplier<T>. Et je comprend à peu près comment ça marche ! L’explication graphique du code mérite vraiment d’être vue.

Et pour les afficher, on peut utiliser une itération externe ou interne. Et sur sa collection magique, on peut aussi (et surtout) utiliser la récursion. La récursion, c’est pratique. Mais en Java, ça peut conduire à des StackOverflow. Parce que Java n’implémente pas la tail call optimization, qui permet des récursions infinies en enlevant certaines méthodes de la pile d’appel. Et Scala l’implémente également, bien sûr (par contre il faut annoter les méthodes où on veut l’appliquer).

Par contre, la tail recursion ne marche que si la méthode récursive est appelée en dernier.

Heureusement, on peut utiliser la technique des trampolines pour permettre cette récursion infinie. En fait, le trampoline, c’est une manière de transformer une récursion en une suite d’appels de méthodes, à grand coups de Supplier<T>.

Et pour finir, Mario se propose de nous implémenter de l’injection de dépendances lazy. Et honnêtement, qund il fait la liste des défauts de l’injection de dépendances classique, ça fait peur.

Et pour ça, il introduit la monade Reader qui est une façon commode d’invoquer une fonction dans un contexte.

Et ça marche d’une façon très chouette, voire même lisible.

Conclusion

L’approche lazy, c’est quand même la meilleure des optimisations parce que ça permet de n’exécuter que le code vraiment utile. Par ailleurs, la démarche fonctionnelle apporte souvent des intérêts en remettant au moment de la compilation des comportements qui doivent y être traités.

Mon avis

j’avais déja vu sur Twitter les slides de Mario, je crois, ou au moins une partie, que je n’avais alors pas parfaitement compris. Avec la présentation en live, j’ai beaucoup mieux compris, et j’ai pris une claque. C’est vraiment chouette.

Devoxxfr – Architecture hexagonale

Pourquoi ? Parce qu’à priori, les présentations sur DDD et l’architecture hexagonale ne parlent pas de framework, alors que les développeurs utilisent pas mal les frameworks.

Chez Saagie, le code s’exécute sur les bonnes machines, grâce à un container manager. Et celui-ci se trompe parfois. Ce container manager est développé avec un ensemble de couches classique. Et le code qui permettait au container manager de choisir où exécuter du code était éclaté dans plusieurs ensembles de service/DAO. Youen a donc refactoré ça dans une méthode à trois ifs.

Comment éviter ça ?

Théoriquement, l’architecture hexagonale permet d’éviter ça.

Domaine

Dans le domaine, il ne faut pas de framework. Des librairies simples peuvent en revanche être utiles.

Le domaine ne doit jamais contenir de code d’infrastructure.

Ports

Les ports sont les moyens d’appeler le domaine. Les ports primaires sont des méthodes du domaine appelables depuis l’extérieur. Et les ports secondaires sont des interfaces définies dans le domaine lui permettant d’appeler des éléments extérieurs.

Adapteurs

Les adapteurs permettent au monde extérieur d’appeler les ports.

Mise en oeuvre

Ensuite, Youen se lance dans une session de live-refactoring pour mettre en place ce type d’archiecture avec une application Spring Boot. Je vous ferai le diagramme de classe plus tard, mais globalement, en deux astuces, il arrive à écrire une application dont les règles métier sont totalement indépendantes des outils techniques utilisés pour communiquer avec le monde extérieur.

Conclusion

Avec ça, Spring est dans son cas nominal, tout comme le domaine. L’architecture hexagonale est donc parfaitement respectée, en utilisant les techniques classiques du monde Java. Le domaine est donc propre, les tests faciles et le code s’adapte beaucoup plus facilement aux échelles. En revanche, les DTO sont un peu lourds, les développeurs doivent être formés et il n’y a pas forcément de starters pour les frameworks.

Mon avis

En fait, l’architecture hexagonale, ça n’a rien de nouveau ni rien de vraiment sexy. Ca consiste surtout à séparer le code réseau du code métier et du code de stockage « d’une façon ou d’une autre ». Et si la méthode choisie par Youen est présentée comme astucieuse, elle est en fait uniquement un bon découpage en classes. Autrement dit ça n’est pas bien compliqué, c’est facilement adaptable et ça me parle puissamment.

Devoxxfr – Effective Java

Là, c’est du sérieux ! Joshua Bloch, l’auteur d’Effective Java vient nous parler de la troisième version de son livre. La deuxième datait de 2008, alors inutile de dire qu’il y a eu des changements depuis.

Joshua ne va pas nous parler de tout son livre (parce qu’il n’a que 45 minutes)

Préférer les lambdas aux classes anonymes

Avant, les classes anonymes, c’était bien (et adapté aux patterns OO). Mais ça n’est plus très pratique avec la programmation fonctionnelle. Donc les lambdas, c’est mieux. Mais avec les comparateurs, c’est un peu plus pratique (puisqu’ils construisent les lambdas à la volée).

Eviter les types sauf si ils sont nécessaires

Dans les lambdas, avec l’inférence de type, oublier les types, c’est cool. Parce que l’inférence de type, c’est magique, mais ça marche. Par contre, ça repose sur les informations des types génériques. Donc utilisez les génériques, sinon les lambdas ne marchent pas.

Utiliser les lambdas dans les enums

Avant, on pouvait faire des enums avec comportement variable grâce aux classes anonymes. Maintenant, grâce aux lambdas, c’est nettement plus facile : il suffit de passer une lambda avec l’opération variable dans le constructeur de l’enum.

Attention aux lambdas

Elles n’ont ni nom, ni documentation. Donc si vous ne comprenez pas ce que fait une lambda, faites-en une méthode. Et les classes anonymes ont quelque avantages :

  • pas besoin d’interface fonctionnelle
  • une classe anonyme a un this, et pas la lambda

Préférez les références de méthodes aux lambdas

Ca rend le code d’autant plus lisible qu’il y a de nombreux paramètres à la méthode. Et dans une lambda, il faut faire attention au nom des paramètres

Parfois, les lambdas sont plus courtes à écrire

Par exemple (x → x) est plus efficace que (Function.identity()).

Attention aux types de référence de méthode

Chaque type de méthode a sa propre déclaration de référence, ce qui peut rendre les choses un peu compliquées (mais toujours plus simples que les lambdas).

Préférez les interfaces fonctionnelles standard

L’exemple est un peu long, mais globalement, l’idée, c’est que si une interface fonctionnelle existe déjà, utilisez-la plutôt que de redéclarer la vôtre. Parce que bon, Java a déjà 43 interfaces fonctionnelles ! Les plus importantes sont UnaryOperator, BinaryOperator, Predicate, Function, Supplier, Consumer. Et puis elles fournissent déjà une API, ce qui limite ce qu’un utilisateur de votre API doit apprendre. En plus, elles fournissent des méthodes par défaut intéressantes (comme Predicate qui fournit combine et negate)

Quand ne pas les utiliser ?

Evidement, quand aucune de ces interfaces ne correspond à votre besoin. Ou alors quand votre interface fonctionnelle définit un contrat clair (comme par exemple Comparator).

Donc, écrivez votre interface fonctionnelle si

  • elle sera beaucoup utilisée
  • elle a un nom clair
  • elle définit un contrat fort
  • elle pourrait bénéficier de méthodes par défaut

Mais n’oubliez pas vos responsabilités : vous définissez une interface, et ça c’est pas de la tarte.

Utilisez les streams avec justesse

Donc un stream, c’est un flux de données traité par un pipeline dans lequel on trouve

  • un générateur
  • zéro ou plus d’opérations intermédiaires
  • et enfin une terminaison

Joshua nous montre ensuite un exemple dans lequel il remplace toutes les collections par des streams. Et c’est illisible. La conclusion est évidente : utilisez les streams avec justesse pour éviter de faire de votre code un bazar sans nom. Notez par ailleurs que les streams de caractères …​ ça ne marche pas. Par ailleurs, dans certains cas, il est difficile de déterminer si les streams seront meilleurs que les itérations traditionnelles.Enfin, la parallélisation des streams donne parfois des résultats désastreux (comme dans son exemple de calcul de nombre de Mersenne où chaque valeur dépend de toutes les valeurs précédents.

Conclusion

Java est maintenant un langage multiparadigme. Choisissez avec soin les parties que vous utilisez.

Vavr, tu vas voir !

Merci à Norsys, qui offre le food truck ce soir (!). (par contre, le chauffage semble être mort dans la soirée).

Guillaume nous vient de chez Saagie (où je connais quelqu’un …​ depuis pas mal de temps) dans l’équipe d’outillage. Et quand il parle, j’ai presque l’impression d’entendre parler un collègue tout aussi barbu, mais plus rancheros …​

La programmation fonctionnelle

C’est de la programmation sans effet de bords, et c’est cool. Et pour faciliter ça, les fonctions sont des éléments essentiels du langage, les variables sont immutables (ça n’a pas de rapport avec la programmation fonctionnelle, en vrai, mais ça fait maintenant partie du canon fonctionnel).

Enfin, ce qui est important pour Guillaume, c’est aussi la transparence référentielle (ou referential transparency) qui garantit que deux appels à la même fonction retourneront le même résultat.

Par exemple, Math.random() ne respecte pas cet aspect, mais Math.max(1, 2) oui.

Java 8 est déja fonctionnel ?

Ben oui, avec les lambda, les streams, les Optional<?>, c’est un début. Mais pour Guillaume il manque des trucs.

Vavr …​ anciennement Javaslang

La librairie a été créée par un développeur Scala frustré par Java. Et le nouveau logo retourne joliment Java …​

Les collections Java, c’est pas pratique

Il y en a en java …​ mais c’est loin d’être pratique. Parce qu’à la base les collections sont conçues pour être mutables (et pas parce qu’ils n’ont pas compris l’immutabilité). Autrement dit, elles ne sont pas joliment immuables, ce qui fâche les puristes. Donc vavr redéfinit ses collections (y compris un Vector).

Typiquement, les méthodes Collection.stream() et Stream.collect() n’existent pas en vavr, parce que les collections sont conçues autour de concepts fonctionnels.

Guillaume mentionne ensuite un point intéressant : en programmation fonctionnelle, on n’aime pas trop les exceptions parce qu’elles ne respectent pas trop le flux d’opérations.

Streams inconsistents

Parfois, les streams Java démarrent leurs opérations un peu en avance. Du coup, deux appels successifs à Stream.map`(…​.)` peuvent balancer des exceptions, ce qui est insupportable pour Guillaume …​ Peut-être pas autant pour moi.

Streams de Map

En Java, utiliser un stream sur une map, c’est l’enfer (il faut faire le stream sur l’ entrySet() et reconstruire la Map au moment du collect). Par contre, en vavr, c’est super facile parce que vavr va mapper les couples clé/valeur vers des Tuple.

Value types

Ils arriveront en Java 10, peut-être (il faudrait demander à Rémi Forax). En revanche, il y en a un paquet dans vavr, inspirés de Scala, évidement.

Option

Hey, mais l’ Option de vavr est sérialisable ! C’est très cool !

Try

Qui permet d’encapsuler du code susceptible de lancer une exception. L’exception sera gentiment catchée et conservée. C’est pas bête du tout.

Either

Là, on est en plein dans la scalaterie. Ca permet de retourner deux types différents en disant que le retour est soit l’un soit l’autre. Bon, jusque là, c’ets un tuple. La particularité d’ Either, c’est qu’on prend comme convention que le bon cas est celui de droite …​ autrement dit le deuxième …​ Je trouve ça pénible et un authentique retour en arrière.

Validation

Qui ressemble furieusement à Bean Validation dans l’objectif …​ mais pas du tout dans l’implémentation, puisque c’est encore un Tuple, voire même un Either particulier.

Des fonctions

vavr ajoute aux Function et BiFunction des interfaces permettant d’utiliser jusqu’à 8 paramètres. Ca n’est pas très pur fonctionnellement, mais c’est bien pratique.

Composition

Mais bien sûr qu’on peut composer des fonctions avec vavr !

Lifting

Chez les fonctionnalistes, c’est la transformation d’une fonction impure en fonction pure. Et je dois bien dire que c’est assez propre.

Application partielle de fonctions

Là aussi, ça fait rêver tous les haskelliens, et c’est assez simplement réalisé. Mais mon voisin pose une excellente question : si j’applique ma fonction partielle à 5 paramètres, lequel reste à utiliser ? Le premier ou le dernier ?

Curryfication

A priori, c’est très semblable à l’application partielle, tout en étant subtilement différent.

Mémoisation

Un peu comme un cache, en fait …​ mais sans avoir besoin de réécrire à chaque fois.

Et maintenant, le pattern matching

Alors là ça va être un peu fou …​ Toujours est-il que, vous le savez maintenant, le pattern matching, c’est un switch sous stéroïdes (mais alors beaucoup de stéroïdes). Par contre, comme vavr reste du Java, c’est la syntaxe habituelle qui est utilisée. Et du coup, c’est moins surprenant puisque l’instruction langage est remplacée par des appels de méthode dans tous les sens. Ca n’est pas forcément très lisible à mon sens, mais bon. En revanche, ce qui est vraiment moche, c’est que le cas d’usage typique, c’est pour faire des instanceOf plus propres de la manière suivante :

Number plusOne = Match(obj).of(
    Case($(instanceOf(Integer.class)), i -> i + 1),
    Case($(instanceOf(Double.class)), d -> d + 1),
    Case($(), o -> { throw new NumberFormatException(); })
);

Bon, ben là, franchement, je suis désolé, mais il n’y a aucune espèce de gain par rapport au simple instanceofdans un if (qui est un foutu antipattern).

Par contre, pour filtrer une liste d’adresses entre valides et invalides, c’est pas mal du tout (merci Either).

Property Checking

On parle ici de la génération automatique de cas de tests. Et il y a pour les différents types de variables simples, des générateurs aléatoires inclus. Ca n’est pas inintéressant …​ mais le test est salement pollué par tout le code supplémentaire nécessaire pour gérer ce contenu aléatoire. Il me semble qu’il y a d’autres outils qui fournissent le même genre de services

Conclusion

La présentation était intéressante et Guillaume sacrément motivé pour nous convaincre (et ça, c’est très cool). Néanmoins, je ne peux pas m’ôter de la tête l’idée que vavr est avant tout un exercice de style, une espèce de figure imposée éventuellement utilisable sur un projet, mais dont ça n’est pas le but premier. Pour moi, le but de ce genre de librairie (comme ça a pu l’être quand j’ai écrit gaedo), c’est avant tout de tester la faisabilité d’un concept. Après, pour l’utiliser, il faut quand même avoir des besoins qui sont à mon sens particulier (surtout si quelqu’un veut sortir de Java 8 pour utiliser vavr). L’idée est néanmoins intéressante, et certains aspects m’ont paru particulièrement séduisants.

Java 9, tu modules ?

Rémi ?

Vous connaissez Rémi ? Il est maître de conférences à Marne la Vallée, développeur d’ASM (j’ignorais ça), d’OpenJDK, expert pour le JCP (et clairement spécialiste des évolutions du langage, pas la partie JavaEE) où il a ajouté invokedynamic, et a travaillé sur les lambda, Jigsaw.

Java 9

Java 9 est la dernière grosse release, parce que c’est pénible. Ca va être remplacé par une release tous les six mois …​ c’est un peu plus rapide. Et donc, dans six mois, on verra var apparaître dans le langage Java.

Les modules

Historiquement, ça devait arriver en Java 6 ou 7 …​ Ca a pris un peu de retard.

Qui veut modulariser le JDK ?

Il y a d’une part des développeurs du JDK qui veulent le faire évoluer, mais ne peuvent pas à cause de toutes les APIs qui utilisent des classes « cachées » du JDK. Et d’autre part …​ des experts de sécurité qui considèrent les failles de la plateforme.

D’autres problèmes

L’enfer du CLASSPATH

Sans outil pour gérer automatiquement le CLASSPATH (genre maven, gradle et autres), c’est l’enfer. Par ailleurs, quand le ClassLoader charge une classe, il scanne linéairement tous les jars et charge la première classe qu’il trouve. Du coup, c’est long. Mais aussi super merdique quand on a deux versions du même artefact. Du coup, on aimerait bien que ce soit géré par la JVM. De la même manière, éviter que les NoClassDefFoundError ne sortent qu’à l’exécution serait une bonne idée pour les développeurs.

Alléger Java

Ce serait sympa de pouvoir faire tourner le code Java sur des plateformes plus légères (typiquement pour l’IoT). Personellement, ça meparaît vaguement possible avec des outils comme ProGuard de limiter la taille des dépendances, mais ce serait mieux d’avoir ça directement dans le JDK. Par exemple, rt.jar contient encore des parseurs XML (qui n’ont pas à être des classes privilégiées) ou CORBA (qui s’en sert encore ?).

Comment modulariser ?

standards
Aucun rapport avec Java, bien sûr

Il y a déja des standards de modules en Java

  • maven
  • gradle
  • JBoss
  • OSGi
  • JavaEE

Du coup, Jigsaw doit fournir un système de modules compatible avec tous ces systèmes prééexistants. Ce qui implique tristement qu’il ne peuvent pas contenir de numéros de version. Et ça, c’est moche.

Modularisons donc !

C’est quoi un module ?

Dans un module, il y a

  • des dépendances
  • et des classes qu’on veut garder invisibles aux autres packages

Par exemple, java se est constitué d’un paquet de modules …​ dont java.compiler. Ce qui signifie qu’il n’y a plus de distinctions JRE/JDK.

Tout ça est décrit dans un module-info.java qui va être compilé comme le reste du code. Ca empêche les modifications ultérieures, ce qui est bien.
A la compilation, il ne peut donc plus y avoir de « split-packages » (des packages déclarés dans deux JARS), ou de dépendances cycliques.

Qu’est-ce qu’on perd ?
  • les « split-package »
  • setAccessible(true) sur une classe d’un module fermé

Au passage, ça permettra enfin à Rémi d’optimiser les champs final (un truc dont j’étais sûr qu’il existait déja pourtant)

Enfin, sauf si on active le flag --illegal-access=permit (qui est parfaitement clair). Notez qu’il y a d’autres valeurs possibles (warn, debug). Bon, un jour, il disparaîtra.

En bonus, les classes JavaEE cachés dans JavaSE (java.xml.bind, java.xml.ws et autres) ne seront plus dans le JDK. Notez que ces classes sont toujours accessibles via des dépendances Maven.
Les classes sun. et com.sun. qui ne sont pas utilisées sont également supprimées.

A noter : L’annotation @ForRemovall (je ne suis pas sûr de l’orthographe) indique les classes qui seront supprimées dans la prochaine version du JDK. C’est une version étendue du classique @Deprecated.

Comment trouver les dépendances

Avec jdeps, par exemple, on peut facilement trouver toutes les dépendances d’un module. Notez que cet outil existe déja dans Java8.

Oui, mais comment je transforme mon application pour Java9 ?

Pour ça, Rémi a développé son application de test : ModuleTools qui lui permet de lire et d’écrire un fichier module-info.java ou module-info.class.Et dans son application, il déclare quelques modules, qui dépendent tous de java.base (comme les classes Java dépendent de java.lang.Object).

Contrairement à maven, un require de module n’est pas transitif (contrairement à maven). Du coup, il y a un require transitif, mais qui est limité à un niveau.

En terme d’organisation de fichiers, il est possible d’avoir plusieurs organisations, dont par exemple plusieurs modules dans le même dossier (ce qui n’est supporté ni par maven, ni par gradle, ni par les IDE).

Il est possible de stocker la version du JAR dans le module-info, et de la réutiliser depuis le code, mais le système de modules n’utilise pas la version.

Comment utiliser un JAR non modulaire ?

Typiquement, une dépendance de Maven Central.On ne peut évidement pas ajouter simplement le JAR dans le CLASSPATH. Il suffit en fait de mettre le jar dans le module-path, et Java va générer automatiquement un module-info, qui permet en plus à ce module automatique de dépendre de JARs du CLASSPATH.Et comment s’appelle ce module ? Soit le nom fourni dans le MANIFEST.MF comme Automatic-Module-Name, soit le nom du JAR sans le numéro de version. Du coup, en ce moment, sur GitHub, c’est la fête à l’Automatic-Module-Name. Au passage, il est possible d’utiliser les JARs modulaires comme des JARs classiques, en les mettant dans le CLASSPATH !

Comment limiter la visibilité ?

Si on veut faire un module interne, il est possible de limiter sa visibilité à certains modules spécifiques, et pas au monde entier. C’est très chouette pour mieux gérer la sécurité. C’est ce qu’ont fait les développeurs du JDK pour la tristement célèbre sun.misc.Unsafe dont une partie a été déplacée dans jdk.unsupported, et une autre partie dans un jdk.internal.misc. Du coup, la réflexion n’est plus possible sur les champs privés (dommage pour gaedo). C’aurait été pénible pour JPA …​ mais les fournisseurs d’implémentation JPA utilisent des agents qui changent le code avant son exécution. Cela dit, la réflexion reste possible grâce à open sur un package ou sur le module.Par ailleurs, le très moche ServiceLoader a été étendu pour fonctionner avec les modules …​ La différence, c’est que la description est un peu propre et intégrée au module-info. Et la description à la fois des fournisseurs d’implémentation et de l’utilisation de ces implémentations doit être fournie. C’est super cool, parce que ça permet de tester les injections de dépendances.

Packager ?

Avec jlink, on peut créer une image à la volée de l’application et de ses dépendances. Pratique pour l’embarqué, évidement, mais aussi pour Docker. Hélas, il ne peut pas y avoir de modules automatiques, parce qu’avec ces modules automatiques, il faut embarquer tout le JDK, et ça limite en plus énormément les possibilités d’optimisation de jlink. C’est un peu la carotte de Jigsaw. Ca permet aussi d’utiliser une JVM minimale de 3 Mo !. Bon, sans JIT, mais sur un Raspberry, c’est pas vraiment utile. Ca permet aussi de garantir la durée d’exécution …​ avec jaotc, qui génère du code natif (qui pourra même se passer de JIT). Le but à terme de cette expérience est (accrochez-vous) de réécrire la JVM en Java (pour se débarasser du code C++).

Evidement jlink (et l’ahead-of-time-compiling) est chouette pour les performances. Et pour l’espace disque.

Conclusion

Modulariser, ça n’est pas si facile, à la fois pour les implémenteurs et les utilisateurs. A ce sujet, la conclusion de Rémi est parfaitement claire : si vous avez une application non-modulaire, n’essayez pas de la modulariser. C’est à la fois long, complexe, et sans valeur ajoutée. En revanche, si vous vous lancez dans une nouvelle application, la modulariser dès le départ peut être utile.

Et merci à nos amis du chtijug pour une soirée sans faille, dans une super salle, et avec une vue vraiment chouette !