5 choses à savoir sur la programmation réactive

Par Clément Escoffier 30 juin 20176 août 2020

Réactif, quel mot surchargé. Beaucoup de choses s’avèrent devenir magiquement réactives de nos jours. Dans ce post, nous allons parler de la programmation réactive, c’est-à-dire un modèle de développement structuré autour de flux de données asynchrones.

Je sais que vous êtes impatient d’écrire votre première application réactive, mais avant de le faire, il y a quelques choses à savoir. L’utilisation de la programmation réactive change la façon dont vous concevez et écrivez votre code. Avant de sauter dans le train, il est bon de savoir où vous allez.

Dans ce post, nous allons expliquer 5 choses sur la programmation réactive pour voir ce que cela change pour vous.

Lorsque vous utilisez la programmation réactive, les flux de données vont être la colonne vertébrale de votre application. Les événements, les messages, les appels et même les échecs vont être véhiculés par un flux de données. Avec la programmation réactive, vous observez ces flux et réagissez lorsqu’une valeur est émise.

Donc, dans votre code, vous allez créer des flux de données de tout et à partir de tout : événements de clics, requêtes HTTP, messages ingérés, notifications de disponibilité, changements sur une variable, événements de cache, mesures d’un capteur, littéralement tout ce qui peut changer ou se produire. Cela a un effet secondaire intéressant sur votre application : elle devient intrinsèquement asynchrone.

Reactive eXtension (http://reactivex.io, a.ka. RX) est une implémentation des principes de programmation réactive pour « composer des programmes asynchrones et basés sur des événements en utilisant une séquence observable ». Avec RX, votre code crée et s’abonne à des flux de données nommés Observables. Alors que la programmation réactive concerne les concepts, RX vous fournit une boîte à outils étonnante. En combinant les modèles d’observateurs et d’itérateurs et les idiomes fonctionnels, RX vous donne des superpouvoirs. Vous disposez d’un arsenal de fonctions pour combiner, fusionner, filtrer, transformer et créer les flux de données. L’image suivante illustre l’utilisation de RX en Java (en utilisant https://github.com/ReactiveX/RxJava).

Bien que RX ne soit pas la seule implémentation des principes de programmation réactive (on peut par exemple citer BaconJS – http://baconjs.github.io), c’est la plus utilisée aujourd’hui. Dans la suite de ce billet, nous allons utiliser Rx Java.

2. Les observables peuvent être froids ou chauds – et cela compte.

À ce stade, vous essayez de voir quels sont les différents flux (ou observables) que vous allez traiter dans votre programme. Mais il y a deux classes de flux : les chauds et les froids. Comprendre la différence est essentiel pour utiliser avec succès la programmation réactive.

Les observables froids sont paresseux. Ils ne font rien jusqu’à ce que quelqu’un commence à les observer (subscribe dans RX). Ils ne commencent à fonctionner que lorsqu’ils sont consommés. Les flux froids sont utilisés pour représenter des actions asynchrones, par exemple, qu’il ne sera pas exécuté jusqu’à ce que quelqu’un soit intéressé par le résultat. Un autre exemple serait le téléchargement d’un fichier. Il ne commencera pas à tirer les octets si personne n’a l’intention de faire quelque chose avec les données. Les données produites par un flux froid ne sont pas partagées entre les abonnés et lorsque vous vous abonnez, vous obtenez tous les éléments.

Les flux chauds sont actifs avant l’abonnement comme un ticker boursier, ou des données envoyées par un capteur ou un utilisateur. Les données sont indépendantes d’un abonné individuel. Lorsqu’un observateur s’abonne à un observable chaud, il obtiendra toutes les valeurs du flux qui sont émises après son abonnement. Les valeurs sont partagées entre tous les abonnés. Par exemple, même si personne ne s’est abonné à un thermomètre, celui-ci mesure et publie la température actuelle. Lorsqu’un abonné s’inscrit au flux, il reçoit automatiquement la prochaine mesure.

Pourquoi est-il si important de comprendre si vos flux sont chauds ou froids ? Parce que cela change la façon dont votre code consomme les éléments véhiculés. Si vous n’êtes pas abonné à une observable chaude, vous ne recevrez pas les données, et ces données sont perdues.

Développez en utilisant les produits les plus précieux de Red Hat

Votre adhésion débloque les produits Red Hat et la formation technique sur le développement d’applications cloud d’entreprise.

JOIGNEZ LE DÉVELOPPEUR RED HAT

Morsures asynchrones mal utilisées

Il y a un mot important dans la définition de la programmation réactive : asynchrone. Vous êtes notifié lorsque des données sont émises dans le flux de manière asynchrone – c’est-à-dire indépendamment du flux principal du programme. En structurant votre programme autour de flux de données, vous écrivez du code asynchrone : vous écrivez du code invoqué lorsque le flux émet un nouvel élément. Les threads, le code bloquant et les effets secondaires sont des sujets très importants dans ce contexte. Commençons par les effets secondaires.

Les fonctions sans effets secondaires interagissent avec le reste du programme exclusivement par le biais de leurs arguments et de leurs valeurs de retour. Les effets secondaires peuvent être très utiles et sont inévitables dans de nombreux cas. Mais ils comportent aussi des pièges. Lorsque vous utilisez la programmation réactive, vous devez éviter les effets secondaires inutiles et avoir une intention claire lorsqu’ils sont utilisés. Adoptez donc l’immuabilité et les fonctions sans effets secondaires. Si certains cas sont justifiés, l’abus d’effets secondaires conduit à l’orage : la sécurité des fils.

C’est le deuxième point important : les fils. Il est agréable d’observer les flux et d’être averti lorsque quelque chose d’intéressant se produit, mais il ne faut jamais oublier qui vous appelle, ou plus précisément sur quel thread vos fonctions sont exécutées. Il est fortement recommandé d’éviter d’utiliser trop de threads dans votre programme. Les programmes asynchrones s’appuyant sur plusieurs threads deviennent un casse-tête de synchronisation difficile se terminant souvent par une chasse aux impasses.

C’est le troisième point : ne jamais bloquer. Parce que vous ne possédez pas le thread qui vous appelle, vous devez être sûr de ne jamais le bloquer. Si vous le faites, vous risquez d’éviter les autres éléments à émettre, ils seront mis en mémoire tampon jusqu’à ce que … le tampon soit plein (la contre-pression peut intervenir dans ce cas, mais ce n’est pas le sujet de ce post). En combinant RX et IO asynchrone, vous avez tout ce dont vous avez besoin pour écrire du code non bloquant, et si vous en voulez plus, regardez Eclipse Vert.x, une boîte à outils réactive favorisant la réactivité et l’asynchronie. Par exemple, le code suivant montre le client Web Vert.x et son API RX pour récupérer un document JSON du serveur et afficher l’entrée du nom:

client.get("/api/people/4").rxSend().map(HttpResponse::bodyAsJsonObject).map(json -> json.getString("name")).subscribe(System.out::println, Throwable::printStackTrace);

Notez la méthode subscribe dans ce dernier extrait. Elle prend une deuxième méthode appelée lorsqu’une des étapes de traitement lève une exception. Attrapez toujours les exceptions. Si vous ne le faites pas, vous passerez des heures à essayer de comprendre ce qui ne va pas.

4. Gardez les choses simples

Comme vous le savez, « Avec un grand pouvoir vient une grande responsabilité. » RX fournit beaucoup de fonctions très cool, et il est facile de se pencher vers le côté obscur. Enchaîner flapmap, retry, debounce, et zip vous donne l’impression d’être un ninja… MAIS, n’oubliez jamais qu’un bon code doit être lisible par quelqu’un d’autre.

Prenons un peu de code…

manager.getCampaignById(id) .flatMap(campaign -> manager.getCartsForCampaign(campaign) .flatMap(list -> { Single<List<Product>> products = manager.getProducts(campaign); Single<List<UserCommand>> carts = manager.getCarts(campaign); return products.zipWith(carts, (p, c) -> new CampaignModel(campaign, p, c)); }) .flatMap(model -> template .rxRender(rc, "templates/fruits/campaign.thl.html") .map(Buffer::toString)) ) .subscribe( content -> rc.response().end(content), err -> { log.error("Unable to render campaign view", err); getAllCampaigns(rc); });

Donné un exemple comme celui-ci est peut être difficile à comprendre non ? Il enchaîne plusieurs opérations asynchrones (flatmap), rejoint un autre ensemble d’opérations (zip). Le code de programmation réactive nécessite d’abord un changement d’esprit. Vous êtes informé des événements asynchrones. Ensuite, l’API peut être difficile à appréhender (il suffit de regarder la liste des opérateurs). N’abusez pas, écrivez des commentaires, expliquez, ou dessinez des diagrammes (je suis sûr que vous êtes un artiste asciiart). RX est puissant, en abuser ou ne pas l’expliquer rendra vos collègues grincheux.

5. Programmation réactive != Système réactif

Probablement la partie la plus confuse. L’utilisation de la programmation réactive ne construit pas un système réactif. Les systèmes réactifs, tels que définis dans le manifeste réactif, sont un style architectural pour construire des systèmes distribués réactifs. Les systèmes réactifs peuvent être considérés comme des systèmes distribués bien faits. Un système réactif se caractérise par quatre propriétés :

  • Réactif : un système réactif doit traiter les demandes dans un temps raisonnable (je vous laisse définir ce qui est raisonnable).
  • Résilient : un système réactif doit rester réactif face aux défaillances (crash, timeout, erreurs 500… ), il doit donc être conçu pour les défaillances et les traiter de manière appropriée.
  • Elastique : un système réactif doit rester réactif sous diverses charges. Par conséquent, il doit monter et descendre en charge, et être capable de gérer la charge avec des ressources minimales.
  • Message driven : les composants d’un système réactif interagissent en utilisant le passage de messages asynchrones.

Malgré la simplicité de ces principes fondamentaux des systèmes réactifs, en construire un est délicat. Typiquement, chaque nœud doit embrasser un modèle de développement asynchrone non bloquant, un modèle de concurrence basé sur les tâches et utilise des E/S non bloquantes. Si vous ne pensez pas d’abord à ces points, cela va rapidement devenir un plat de spaghetti.

La programmation réactive et l’eXtension réactive fournissent un modèle de développement pour apprivoiser la bête asynchrone. En l’utilisant à bon escient, votre code va rester lisible, et compréhensible. Cependant, l’utilisation de la programmation réactive ne transforme pas votre système en un système réactif. Les systèmes réactifs sont le prochain niveau.

Conclusion

Nous arrivons enfin à la fin de ce post. Si vous voulez aller plus loin et êtes intéressé par le réactif, je vous recommande de jeter un coup d’œil à Eclipse Vert.x – une boîte à outils pour construire des systèmes réactifs et distribués (http://vertx.io), et au minibook Reactive Microservices in Java disponible auprès de https://developers.redhat.com/books/building-reactive-microservices-java. La combinaison de Vert.x et de Reactive eXtension libère votre superpuissance réactive. Vous pouvez non seulement utiliser la programmation réactive mais aussi construire des systèmes réactifs et avoir accès à un écosystème passionnant et en pleine expansion.

Happy coding!

Téléchargez la cheat sheet Eclipse Vert.x, cette cheat sheet fournit des détails étape par étape pour vous permettre de créer vos apps comme vous le souhaitez.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.