Chapitre 16
Les files de message

La communication entre les systèmes grâce à des événements.
Temps de lecture : 5 minutes


Lors de l'introduction aux API, nous avons vu qu'un système informatique est constitué de dizaines de briques logicielles et de composants qui doivent communiquer entre eux. Leur API représente l'ensemble des contrats, des fonctionnalités, qu'ils exposent publiquement.

Jusqu'à présent, nous avons parlé des entrées d'un composant, c'est-à-dire ce qu'il expose pour être directement appelé.

Exemple : "envoie-moi la requête HTTP GET /offre/42" pour que je te retourne les détails de l'offre n°42".

Nous allons voir dans ce chapitre qu'un composant peut aussi produire des sorties : c'est lui qui déclenche une communication pour émettre une information. Les développeurs les utilisent pour :

  1. Découper un processus et déclencher des tâches asynchrones.
  2. Séparer les responsabilités entre les composants, pour construire des systèmes indépendants et maintenables sur le long terme.

Les notifications push

Partons directement sur un exemple :

Nous avons un site d'e-commerce, le client vient de payer, il faut maintenant lancer toute la chaîne de traitement de sa commande : analyse anti-fraude, envoi du colis...

  • Chaque maillon de cette chaîne est développé et maintenu par une équipe différente de développeurs, dans une brique logicielle dédiée.
  • La chaîne est séquentielle : les traitements s'enchaînent dans l'ordre, l'un des maillons peut stopper la progression.
  • Tout ce traitement doit être asynchrone de la navigation du client. Maintenant qu'il a payé, il peut sortir du parcours, nous le recontacterons si besoin.

La brique de paiement a donc terminé son traitement et veut passer la main à la brique anti-fraude.

On peut penser cette architecture de différentes façons. Il y a 15 ans, on serait surement passé par une source de données partagée ou un export dans un fichier. Un truc du style : dans la base de données, le paiement passe le statut de la commande en "paiement validé", l'anti-fraude vient ensuite requêter toutes les deux minutes les commandes avec ce statut.

Ça fonctionne, mais c'est loin d'être optimal :

  • C'est l'équivalent d'un enfant qui demande en boucle si on est bien arrivé.
  • Ça ralentit la chaîne de potentiellement deux minutes. S'il y a vingt étapes, ça décale d'autant la livraison du colis.
  • La base de données est une adhérence commune entre les deux briques. Et ça, c'est mal ! Nous reviendrons sur ce point dans le chapitre sur les microservices.

Il serait donc plus intéressant de mettre en place un système dans lequel le paiement pousse directement l'information à l'anti-fraude.

Ces notifications push sont possibles avec une simple requête HTTP. Le paiement appelle directement l'API de l'anti-fraude : "Hey, anti-fraude, vérifie que le client de la commande n°42 n'est pas un fraudeur".
Simple et efficace, c'est ce qu'on fait pour 90% des communications inter-briques !

Néanmoins, cette approche a deux inconvénients majeurs :

  1. Alors qu'il est plus haut dans la chaîne, le paiement a une adhérence forte et manipule directement l'API de l'anti-fraude. Lui, il a terminé sa part du travail, ce n'est normalement pas sa responsabilité de savoir qui va prendre le relai et comment.

    Pour inverser le sens de cette dépendance, on peut mettre en place un système d'observation : le paiement donne de la visibilité aux autres briques sur ses événements internes. Il peut proposer, via son API, la possibilité de s'inscrire à "j'ai terminé de traiter cette commande". À chaque événement, il enverra la même requête HTTP à tous les observateurs, sans savoir qui ils sont. C'est à eux de s'adapter pour comprendre et traiter la requête.
  2. Les requêtes HTTP sont synchrones, le paiement se met en pause tant que l'anti-fraude ne lui a pas répondu.

    En l'occurrence, dans notre cas, puisque le paiement se fiche de connaître la suite, il n'attend aucune réponse de la part de l'anti-fraude. L'idéal pour lui serait que l'anti-fraude stocke cette notification et accuse directement réception le plus rapidement possible sans lancer de traitement. Ça obligerait l'anti-fraude à gérer en interne deux étapes : stockage, puis détection des nouvelles entrées pour lancer le traitement. Oups, on retrouve les mêmes problématiques qu'il y a 15 ans avec des requêtes toutes les deux minutes.

On utilise particulièrement ce système d'observation et la séparation en deux étapes lorsqu'on travaille avec des partenaires et qu'HTTP est l'une des seules options.
Exemple : Apple me pousse une notification pour me prévenir que le client a annulé son abonnement iOS.

De meilleurs choix s'offrent à nous pour faire communiquer de façon asynchrone des briques internes indépendantes.

Les files de messages

Imagine le tapis roulant quand tu déposes tes bagages à l'aéroport. Une personne pose des valises sur le tapis, une autre est à l'affut pour les mettre dans un véhicule dès qu'elles arrivent. Les deux font leur travail en toute indépendance, sans réellement savoir ce qu'il se passe à l'autre bout. Le gestionnaire de l'aéroport gère plein de tapis.

C'est le principe de la file de messages :

  1. La brique de paiement transmet un message via un courtier de messages, un logiciel qui gère des files. Elle lui demande de produire sur la file "paiement terminé" le message "commande n°42".
  2. Ce courtier, le plus connu étant RabbitMQ, s'occupe de stocker les messages jusqu'à ce que quelqu'un les lise ou jusqu'à leur expiration.
  3. La brique anti-fraude se branche en continu sur la file "paiement terminé". Elle reçoit les messages non-lus au fur et à mesure et peut les consommer à son rythme.
Ce logiciel est une dépendance commune entre les deux briques, en quoi c'est mieux que la base de données qu'on utilisait il y a 15 ans ?

Les deux briques sont en effet dépendantes du courtier et de la file, mais c'est une adhérence purement technique.
Le système utilisant le statut des commandes dans la base de données crée, lui, une adhérence basée sur un concept métier : c'est beaucoup plus fragile et susceptible d'évoluer.

Passer par un courtier offre aussi plusieurs avantages techniques : rapidité, gestion d'une charge importante, garantie de livraison...

Ce système a cependant une limite : une fois un message consommé, il est détruit, il ne peut donc y avoir qu'un seul consommateur.

Complétons notre exemple :

En plus de l'anti-fraude, on veut tenir au courant une brique financière qu'un paiement est terminé.

Si on continuait à utiliser un simple système de files, il nous faudrait dupliquer le message dans deux files. Ça prendrait plus de place et, surtout, ça enlèverait l'indépendance de la brique de paiement : ça l'obligerait à adapter son comportement en fonction des consommateurs de ses messages.
Pas top !

Les files de messages sont ainsi beaucoup utilisées pour découper un traitement en plusieurs étapes asynchrones. Une situation dans laquelle on est sûr de n'avoir qu'un seul consommateur.
Ce que nous essayons de faire dans notre exemple s'apparente plutôt à l'émission d'un événement "paiement terminé". Pour ça, nous allons utiliser un système un peu plus évolué.

Publisher / Subscriber

C'est le principe de la radio, un message est ouvertement diffusé à tous. Ça assure une parfaite indépendance de la brique qui produit le message. Le problème, c'est que, comme pour une radio, il faut écouter en permanence pour ne pas louper d'information : il n'y a pas de sauvegarde.

Ce comportement est utile pour des systèmes qui veulent du temps réel et pour lesquels les données passées ne sont plus utiles. Par exemple, l'affichage sur mon site des prix en euros et dollars en fonction du taux de change actuel.

Nous, on ne veut surtout pas louper de paiement, sous peine d'arrêter la chaîne et de ne jamais envoyer son colis au client.

En l'état, le concept du pub/sub ne répond donc pas à nos besoins. Il nous faudrait un système qui garde ce mode "radio", tout en le mixant avec le principe des files de messages.

Plateforme de flux d’événements

Pour résumer ce qu'on vient de se dire, l'outil que nous recherchons doit permettre :

  • À la brique de paiement de produire un événement métier "paiement terminé", sans se soucier de qui va le consommer.
  • Aux briques anti-fraude et financière de recevoir en temps réel cet événement et de le traiter à leur propre rythme. Elles doivent pouvoir rattraper les événements manqués en cas de panne : ils doivent donc être sauvegardés.

En 2010, les ingénieurs de LinkedIn avaient les mêmes besoins que nous.
LinkedIn, ce sont des milliards d'événements métiers chaque jour : nouveau commentaire, demande de connexion, abonnement...
Comment mettre à jour à la fois la recommandation, le moteur de recherche et les statistiques lorsqu'un utilisateur clique sur un profil ? Comment envoyer une notification à tous les abonnés suite à un nouveau post ?

Ces ingénieurs sont arrivés à la même conclusion que nous : les solutions existantes ne sont pas satisfaisantes.

Ils ont alors développé Kafka, une plateforme de flux d'événements, un outil sur mesure qui répond à tous ces besoins :

  • Un producteur envoie un message dans un topic, une sorte de file.
  • Les consommateurs peuvent se brancher sur ce topic et lire les messages un par un. Kafka permet à plusieurs consommateurs de lire les mêmes messages, chacun à son propre rythme.
  • Les messages sont stockés physiquement dans des journaux, on peut configurer une durée de rétention de plusieurs jours. En cas d'incident, un consommateur peut ainsi se rebrancher sur le topic et reprendre là où il s'est arrêté.
  • Kafka est un système distribué : il sait répartir son travail sur plusieurs machines et peut absorber une très forte charge.

C'est exactement le mix entre le pub/sub et la file de messages que nous cherchions ! En plus, Kafka est devenu open-source en 2011, on peut l'utiliser sur tous nos projets.

Kafka a fortement popularisé le concept de streaming d'événements. C'est devenu un indispensable pour développer des briques indépendantes et maintenables. Il a révolutionné la conception des architectures backend autour d'événements métiers en ouvrant une nouvelle logique de pensée pour les développeurs.
Exemple : une fois envoyé, un événement ne peut plus être modifié ni annulé. Tout est incrémental, il faut produire un second événement complétant ou invalidant le premier.

Au début du chapitre tu nous parlais d'API, c'est quoi le rapport avec Kafka ?

Souviens-toi de cette phrase "Leur API représente l'ensemble des contrats, des fonctionnalités, qu'ils exposent publiquement.". Tous les messages, en particulier les événements métiers, entrent dans cette définition.

Comme pour toute communication sortante :

  • Il faut mettre en place une stratégie de modification pour assurer la rétrocompatibilité : versioning, interdiction de supprimer un champ...
  • Elle doit être documentée.
  • Elle peut être monétisable.
  • Elle doit être pensée comme un produit intuitif et répondant à un besoin.