Introduction
Dans le cadre d’un projet chez l’un de nos clients, on nous a chargé de réaliser une API. Celle-ci avait la responsabilité d’agréger différentes données venant de plusieurs API.
À des fins de performance, nous avons choisi de paralléliser les appels et d’utiliser les Futures.
Présentation
La librairie Vavr est utilisable à partir de la version 8 de Java.
Cette librairie a pour but d’améliorer le coté programmation fonctionnelle de Java 8 en ajoutant notamment des types de données immuables et des structures de contrôles orientés fonctionnelles.
C'est parmi ces types de données immuables que nous trouvons notre Future, objet de cet article.
Une Future va permettre de réaliser une action dont le résultat sera disponible dans le futur.
Lorsque l'action de la Future est terminée, trois états de celle-ci sont possibles :
completed: lorsque la Future s'est terminée
success: lorsque la Future s'est terminée avec succès
failure: lorsque la Future s'est terminée en erreur
Il est possible d'ajouter des callbacks afin d'intercepter ces trois états.
Chez notre client, cela permettait de lancer X actions (Futures) permettant de récupérer des données, attendre le résultat de chacune et agréger toutes les données. Pour mettre en place ceci, nous avons créé une liste de Futures, les avons packagées dans une séquence. Nous devons attendre le résultat de toutes les Futures et avons donc bloqué les différents Thread grâce à la méthode .await(). Ensuite, la méthode .get() nous permet d’obtenir le résultat de la Future.
Illustration : la course d'animaux
Afin de lancer la course et de vous présenter les Futures, nous allons réaliser un test unitaire.
Création de la classe Java Animal avec son nom, sa famille, son type et vitesse moyenne. L'annotation @Data vient du projet Lombok : une librairie de génération de code. Ce projet fera l'objet d'un autre article.
package com.avalonlab.vavr.bean; | |
import lombok.Data; | |
@Data | |
public class Animal { | |
private String nom; | |
private TypeAnimal type; | |
private String famille; | |
private int vitesse; | |
public Animal(String nom, TypeAnimal type, String famille, int vitesse) { | |
this.nom = nom; | |
this.type = type; | |
this.famille = famille; | |
this.vitesse = vitesse; | |
} | |
} |
package com.avalonlab.vavr.bean; | |
public enum TypeAnimal { | |
CHIEN, | |
CHAT, | |
LAPIN, | |
SOURIS, | |
ANE | |
} |
Ici, nous initialisons 5 animaux qui sont de famille et de vitesse différentes.
package com.avalonlab.vavr; | |
import com.avalonlab.vavr.bean.Animal; | |
import com.avalonlab.vavr.bean.TypeAnimal; | |
import org.junit.Before; | |
import io.vavr.collection.List; | |
public class VavrTest { | |
protected List<Animal> animaux; | |
protected Animal chien; | |
protected Animal chat; | |
protected Animal lapin; | |
protected Animal souris; | |
protected Animal ane; | |
@Before | |
public void init() { | |
chien = new Animal("Idéfix", TypeAnimal.CHIEN, "Canidés", 60); | |
chat = new Animal("Garfield", TypeAnimal.CHAT, "Félin", 38); | |
lapin = new Animal("Panpan", TypeAnimal.LAPIN, "Léporidés", 30); | |
souris = new Animal("Ratatouille", TypeAnimal.SOURIS, "Rongeurs", 10); | |
ane = new Animal("Bourriquet", TypeAnimal.ANE, "Équidés", 15); | |
animaux = List.of(chien, chat, lapin, souris, ane); | |
} | |
} |
Le test unitaire:
package com.avalonlab.vavr; | |
import java.time.LocalDateTime; | |
import java.time.format.DateTimeFormatter; | |
import java.util.ArrayList; | |
import com.avalonlab.vavr.bean.Animal; | |
import com.avalonlab.vavr.bean.TypeAnimal; | |
import org.junit.Test; | |
import io.vavr.collection.List; | |
import io.vavr.collection.Seq; | |
import io.vavr.concurrent.Future; | |
import static org.assertj.core.api.Assertions.assertThat; | |
public class VavrFutureTest extends VavrTest { | |
private int distanceDeLaCoursEnM = 100; | |
private List<String> resultats = List.empty(); | |
@Test | |
public void lancerLaCourse() { | |
// On prépare les futures des animaux. | |
Future<Animal> futureDuChien = preparerUnAnimal(chien); | |
Future<Animal> futureDuChat = preparerUnAnimal(chat); | |
Future<Animal> futureDuLapin = preparerUnAnimal(lapin); | |
Future<Animal> futureDeLaSouris = preparerUnAnimal(souris); | |
Future<Animal> futureDeLane = preparerUnAnimal(ane); | |
// On ajout les futures des animaux dans un tableau. | |
List<Future<Animal>> futures = List.of(futureDuChat, futureDuChien, futureDuLapin, futureDeLaSouris, futureDeLane); | |
// Lorsque les animaux arriveront à la ligne d'arrivée, on inscrit l'animal sur le tableau des resultats. | |
futures.forEach(future -> { | |
future.onComplete(resultat -> inscrireLeResultat(resultat.get())); | |
}); | |
// Initialisation de la course | |
Future<Seq<Object>> toutesLesFutures = Future.sequence(futures); | |
// Lancement de la course et attente que tous les animaux ont terminés la course | |
toutesLesFutures.await(); | |
System.out.println(getHeure() + " : Course terminée."); | |
assertThat(resultats).containsExactly(TypeAnimal.CHIEN.name(), TypeAnimal.CHAT.name(), TypeAnimal.LAPIN.name(), TypeAnimal.ANE.name(), TypeAnimal.SOURIS.name()); | |
} | |
private Future<Animal> preparerUnAnimal(Animal animal) { | |
Future<Animal> future = Future.of(() -> { | |
System.out.println(getHeure() + " : Préparation de l'animal " + animal.getNom()); | |
long chronoEnSeconde = calculerChronoEnSeconde(animal, distanceDeLaCoursEnM); | |
Thread.sleep(chronoEnSeconde * 1000); | |
return animal; | |
}); | |
return future; | |
} | |
private void inscrireLeResultat(Animal animal) { | |
resultats.append(animal.getType().name()); | |
long chronoEnSeconde = calculerChronoEnSeconde(animal, distanceDeLaCoursEnM); | |
System.out.println(getHeure() + " : L'animal " + animal.getNom() + " a fini la course en " + chronoEnSeconde + " secondes."); | |
} | |
private long calculerChronoEnSeconde(Animal animal, int distanceEnM) { | |
double chronoEnHeure = 3.6 * distanceEnM / animal.getVitesse(); | |
return (long) chronoEnHeure; | |
} | |
private String getHeure(){ | |
LocalDateTime now = LocalDateTime.now(); | |
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); | |
return now.format(formatter); | |
} | |
} |
Résultat du test unitaire :
Conclusion
Grâce à ce test unitaire, nous avons utilisé la librairie Vavr, disponible à partir de Java 8, afin de paralléliser des traitements. Cette librairie ne permet pas uniquement l’utilisation de Future mais elle implémente aussi des Collections, List, Map, Option, Try …
La documentation de Vavr est disponible ci-dessous afin d’approfondir cette librairie.
Top comments (0)