Devoxx France.
Cette session comportait six talks que nous allons résumer ici.
Said Boudjelda - Linkedin
Quarkus est un framework Java pour les environnements cloud et serverless et permet le développement d'applications rapides et légères. Il est particulièrement adapté aux architectures basées sur des microservices et aux conteneurs, avec un démarrage rapide et une empreinte mémoire réduite. Quarkus prend en charge les standards Java et est conçu pour fonctionner nativement avec Kubernetes.
Cucumber permet de faire du Behavior driven development (BDD). On écrit des tests en langage naturel via Gherkin, qui sont donc lisibles et compréhensibles par des personnes non-techniques. Comme il n'est pas raisonnable que les développeurs d'un projet deviennent des experts du domaine, et que les experts du domaine ne peuvent pas devenir développeur. La personne non-technique peut comprendre les tests, voire même les écrire. La collaboration entre les équipes développement et métier est facilitée.
Example de test Gherkin
En tant que bibliothécaire
Je veux pouvoir ajouter des livres à la bibliothèque
Afin que les utilisateurs puissent les emprunter
Scénario: Ajouter un nouveau livre à la bibliothèque
Étant donné que je suis connecté en tant que bibliothécaire
Et que je suis sur la page d'ajout de livre
Quand je saisis le titre "Le Petit Prince"
Et que je saisis l'auteur "Antoine de Saint-Exupéry"
Et que je saisis l'année de publication "1943"
Et que je clique sur le bouton "Ajouter"
Alors le livre "Le Petit Prince" par "Antoine de Saint-Exupéry" est ajouté à la bibliothèque
Et un message de confirmation "Le livre a été ajouté avec succès" est affiché
Les tests écrits servent aussi de "Living documentation".
Said est contributeur sur le projet Quarkus Cucumber qui est un plugin permettant d'intégrer Cucumber dans Quarkus. Ce talk est un appel à l'aide, le plugin ayant besoin de contributeurs.
Toute personne est libre de contribuer au projet sur l'une de ces différentes thématiques :
Pour ceux qui veulent débuter dans l'open source, mettre à jour la documentation est une bonne porte d'entrée.
On peut retrouver les slides sur le repository Github du talk.
Salah Eddine El Mamouni - Linkedin
Derrière chaque process se trouvent des tâches répétitives. Ces tâches peuvent être automatisées avec des solutions de workflow.
Ce talk nous apprend comment déployer, exécuter et superviser des workflows.
Un workflow est un ensemble de tâches séquentielles réalisées pour atteindre un objectif spécifique. Ces tâches peuvent être automatisées ou être lancées manuellement.
Pour décrire le workflow, on va utiliser un standard de modélisation graphique qui permet de le représenter visuellement. Ce standard offre une notation compréhensible à la fois par les développeurs et les analystes du métier.
Ce standard s'appelle BPMN.
Une fois le workflow défini, il faut pouvoir l'exécuter. Il existe différents acteurs tels que Activiti, jBPM, Kogito et Camunda. Salah Eddine nous recommande Camunda qui est aujourd'hui le leader du marché. Il s'agit d'une plateforme de gestion des processus métier (BPM - business process management) permettant d'exécuter et de monitorer les workflows.
Ses principales fonctionnalités sont:
Il est possible de réaliser des workflows qui vont discuter avec différentes APIs et est donc adapté à un environnement en microservices.
Yifang Dong - Linkedin
Yifang commence par rappeler que "les tests servent de documentation vivante sur le fonctionnement de chaque composant. Tester les composants individuellement garantit que tous les aspects de chaque module sont pris en compte. Lorsqu'un composant est testé indépendamment, il est plus facile d'identifier l'origine des problèmes"
Comment bien tester lorsque le code que nous voulons tester contient de l'asynchrone ? Prenons l'exemple d'une notification qui est envoyée en asynchrone. Nous voulons vérifier que cette notification est bien arrivée.
Un test basique ne fonctionne pas car son exécution se termine avant que la notification ne soit reçue. Une implémentation naïve pourrait d’ajouter un temps d’attente via une instruction sleep afin de laisser le temps à la notification d'arriver. Mais cela rend le test instable: il peut échouer si la notification arrive après la fin du sleep. Cela rend également le test inefficace car on doit attendre la fin du sleep alors que la notification est peut être arrivée depuis un moment.
Deux solutions existent : le CompletableFuture et le CountDownLatch
Le CompletableFuture est une classe Java du package java.util.concurrent introduite en Java 8 pour faciliter la programmation asynchrone en permettant des opérations futures et la composition de tâches.
On peut utiliser la méthode bloquante get. Lorsqu’elle est utilisée, le Thread qui l'appelle suspend son exécution jusqu'à ce que la tâche représentée par le CompletableFuture soit terminée et que son résultat soit disponible. Dans notre exemple, on attend jusqu’à la réception de la notification.
Ici le Thread n'attend que le temps nécessaire pour que la tâche s'exécute et se termine. Le blocage dépend donc de la durée d'exécution de la tâche. Si la tâche est rapide, le blocage sera de courte durée. Si la tâche est longue, le thread sera bloqué plus longtemps.
public Future<String> calculateAsync() throws InterruptedException {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
Thread.sleep(100);
completableFuture.complete("Completed !");
return null;
});
return completableFuture;
}
Future<String> completableFuture = calculateAsync();
String result = completableFuture.get();
assertEquals("Completed !", result);
Il existe des alternatives non bloquantes. CompletableFuture permet de chaîner plusieurs étapes de traitement avec thenApply, thenAccept, thenCompose et whenComplete. Cela facilite la composition de plusieurs opérations mais peut également rendre le code plus complexe et difficile à lire, voir à des “callback hells”.
CompletableFuture
.supplyAsync(() -> "Completed !")
.thenAccept(result -> System.out.println(result));
Le CountDownLatch en Java est une classe du package java.util.concurrent qui permet de synchroniser un ou plusieurs threads en leur permettant d’attendre jusqu'à ce qu'un certain nombre d'événements se produisent.
Il permet de coordonner l'exécution de threads dans des situations où un ou plusieurs threads doivent attendre qu'un autre thread termine son travail avant de pouvoir continuer.
public class Worker implements Runnable {
private List<String> output;
private CountDownLatch countDownLatch;
public Worker(List<String> output, CountDownLatch countDownLatch) {
this.output = output;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
doSomeWork();
output.add("Added by Worker");
countDownLatch.countDown();
}
}
@Test
public void countDownLatchExample()
throws InterruptedException {
List<String> output = Collections.synchronizedList(new ArrayList<>());
CountDownLatch countDownLatch = new CountDownLatch(3);
List<Thread> workers = Stream
.generate(() -> new Thread(new Worker(output, countDownLatch)))
.limit(3)
.collect(toList());
workers.forEach(Thread::start);
countDownLatch.await();
output.add("Latch released");
assertThat(output)
.containsExactly(
"Added by Worker",
"Added by Worker",
"Added by Worker",
"Latch released"
);
}
CountDownLatch nous fournit deux méthodes: await pour attendre la fin des processus asynchrones et countDown qui permet de décrémenter le compte du latch. Le await se termine une fois que le compte est à zéro.
Nous avons ici vu deux méthodes pour gérer notre test en asynchrone. Une autre solution est de se passer de l'asynchrone. On change alors l'implémentation du service pour qu'il n'ai plus la responsabilité de lancer lui-même le thread. Il reçoit un Runnable . Et notre test peut injecter un Runnable qui n'est pas asynchrone, tandis que l'implémentation métier injecte un Runnable qui lancera un Thread.
Attention cependant, car on peut vouloir tester le comportement en asynchrone via un Thread. L'implémentation du test peut donc dépendre de ce que nous voulons réellement tester.
Salathiel Genese Yimga Yimga - Linkedin
Les JPMS - Java platform Module systems - ont été pensés pour introduire une modularité dans Java en 2005 pour répondre au besoin de diviser le JDK en modules pour le rendre plus léger et plus facile à gérer.
Les travaux débutent en 2008 sous le nom de “Projet Jigsaw” avec pour but une sortie sur Java 7. En raison de sa complexité et des autres priorités, le projet ne sera finalisé que sur Java 9 en 2017.
Les JPMS restent une partie méconnue de Java et ne sont donc pas très utilisés. Pourtant, JPMS peut offrir de nombreuses choses aux développeurs Java.
Le JDK utilise les JPMS et est donc modularisé. Cela permet d'utiliser certaines parties du JDK sans tout importer. Il permet des JRE (Java Runtime Environment) plus petits puisque le JRE lui-même est organisé en modules. Cela permet de n'inclure que les modules nécessaires pour l'application et réduit donc la taille du JRE. C'est utile dans les environnements où les ressources sont limitées, comme les conteneurs Docker ou les déploiements cloud.
JPMS permet une encapsulation forte du code en exposant certains packages uniquement. Pour les ressources, les fichiers assets sont séparés.
Un frein à l'adoption est un manque de compatibilité de JPMS avec certaines bibliothèques tel que JUnit. Enfin, pour beaucoup de projets, les avantages de la modularisation (meilleure encapsulation, réduction des conflits de classes) ne sont pas toujours jugés suffisants pour justifier l'effort de migration.
Retrouvez les slides de ce talk.
Marc Guiot - Linkedin
Marc expose une problématique qu'il a connu il y a quelques années sur un site eCommerce qui recevait de nombreux Webhooks. Pour des besoins d'optimisation, les triggers suite à la réception de Webhooks externes ne devaient se faire que lorsque nécessaire.
Prenons en exemple une Webhook notifiant à intervalle régulier l’état de l’ensemble des stocks d’une boutique.
La mise à jour des informations des stocks sur nos applications se fait toutes les cinq minutes environ. Durant cet intervalle de cinq minutes, plusieurs Webhooks peuvent être reçues.
On souhaite calculer la différence entre la Webhook dernière Webhook prise en compte et l’ensemble des Webhooks reçue depuis. S'il y a une différence, on notifie nos applications.
Comment calculer cette différence ?
La solution retenue est différente. Marc et son équipe ont développé GlobsFramework. C’est un framework metamodel conçu pour utiliser des Generic Lightweight objects (Globs). Un Glob est essentiellement un objet de type Map permettant une modélisation du schéma des données. Les fields du Glob peuvent être visités en utilisant le “visitor pattern”.
public static class ProductType {
public static GlobType TYPE;
public static LongField id;
public static StringField title;
public static DoubleField price;
public static BooleanField published;
}
Pourquoi créer ce framework metamodel plutôt que l'introspection ?
Pour ceux qui auraient des problématiques similaires, un participant met en avant JaVers - "The Leading Framework for Object Audit and Diff in Java".
Manuel Camargo - Linkedin
Manuel est un fan de Test Driven Development (TDD) que nous vous avons présenté dans l’article “Le double cycle TDD : ne sous-estimez plus l'étape de refactoring”.
Il a décidé d'apprendre à GitHub Copilot à en faire. L'IA est pourtant souvent décriée pour produire du code de mauvaise qualité.
Revenons tout d'abord rapidement aux bases du TDD. Ce processus de développement fonctionne en cycle composé de trois phases : Red, Green et Refactor. Écrire un test qui échoue (Red), écrire juste assez de code pour que le test passe (Green), puis re-factoriser le code tout en s'assurant que les tests passent toujours (Refactor). TDD permet d'améliorer la qualité du code et de garantir que chaque fonctionnalité est correctement testée dès sa création.
On s'est rendu compte que les IA répondaient bien mieux si on leur demandait d'écrire les différentes étapes de réflexion, ce qui est exactement un principe du TDD. Cette technique appelée “prompt chaining” découpe un problème complexe et permet d'obtenir de meilleurs résultats, avec moins d'hallucinations de la part de l'IA.
La présentation commence par une demande simple, sans TDD, d'écrire la logique et les tests correspondants. L'organisation du code générée est mauvaise : le code métier est dans la couche repository.
Les tests fournis par Copilot passent tous. Mais aucun test n'a la même forme et ils ne sont pas cohérents. Manuel écrit des tests supplémentaires pour vérifier que le code généré est vraiment correct. Ses tests ne passent pas car Copilot n'a pas géré les cas limites dans le code métier, et n'a pas non plus généré de tests pour les valider.
"À première vue, Copilot est comme un dev avec énormément de connaissances mais aucune intelligence". Est-ce que l'approche TDD peut y changer quelque chose ?
Comme on l'a vu, les tests sont de mauvaise qualité. On doit donc commencer par expliquer à Copilot ce qu'est un bon test. On commence par expliquer le contexte du projet : le métier, l'architecture, les standards utilisés. On insiste particulièrement sur les standards de tests unitaires. On peut également fournir un exemple de test existant qui applique ces standards.
On lui demande de générer un premier test qui échoue (red). Puis on demande l'implémentation du test. Dans le prompt, on fourni le nom du test voulu afin d'orienter son implémentation.
On passe ensuite à l'étape Green. Mais Copilot va avoir tendance à écrire plus de code que ce dont on a besoin. En TDD on a besoin du strict minimum pour faire passer le test. Il faut lui rappeler cette règle.
Enfin vient l'étape du Refactor. Manuel spécifie qu'il vaut mieux demander de refactorer. Si on lui demande d’améliorer le code, Copilot va avoir tendance à rajouter du code non nécessaire tel que du logging. On lui demande également de faire cinq propositions d'améliorations différentes. Le développeur choisit celle qui lui plait le plus. Parmi ces cinq propositions, il peut y en avoir une seule d'intéressante.
Le retour sur cette expérience est que ça fonctionne. "Faire du TDD avec Copilot c'est comme faire du peer programming où Copilot est le driver et on est le navigateur". Attention cependant, les tests réalisés l'ont été sur des cas qui restent assez simples et pas sur des algorithmes complexes.
Le principal problème rencontré par Manuel est l'ergonomie dû à l'outillage utilisé (Copilot sur IntelliJ IDEA), qui n'existe pas avec d'autres solutions.
Par exemple, on peut désormais utiliser les Copilot extensions pour ajouter nos propres standards à Copilot et qu'il les retienne. Certains IDE comme vs code ont une intégration bien plus poussée qui permet par exemple d'avoir des propositions de code directement dans l'éditeur. On parle aussi d'autres IDE spécialisés dans l'IA comme Cursor.
Si vous souhaitez intégrer de l’IA dans votre processus de développement, retrouvez notre article “AI Augmented Developer: Intégrer la GenAI dans la toolbox des développeurs”.
Ce premier Paris JUG de l’année nous a permis de découvrir de nouveaux speakers. Rappelons que la présentation est coupée à l’issue de quinze minutes. L’exercice n’est donc pas évident. Certains n’ont pas pu finir complètement, mais repartent avec une précieuse expérience : ne pas finir sur le plus important.
Cette initiative est importante pour permettre à de nouvelles personnes de se lancer dans les talks.
Les six talks présentés couvrent une gamme variée de sujets. Chaque intervenant a pu apporter sa perspective sur les problématiques rencontrées dans l'écosystème Java.
La soirée s'est terminée par le vote du public pour déterminer le speaker qui participera à Devoxx France. C’est Manuel Camargo qui ira y présenter son talk “Apprenez à votre IA à faire du TDD” du 16 au 18 avril 2025.
Avec le soutien d’OCTO pour l'année 2025, Paris JUG continue de renforcer les liens de la communauté Java.
Nous remercions tous les participants et intervenants pour leur contribution à cet événement réussi, et sommes impatients de voir ce que cette année nous réserve encore.