Dans ce nouveau billet, nous allons parler de la mise en œuvre concrète des principes fondamentaux de l’architecture événementielle.
Topologie
La topologie décrit la manière dont le bus est mis en œuvre sur le courtier de messages.
En tant que courtier de messages, nous avons choisi RabbitMQ pour sa fiabilité et sa facilité d’utilisation.
Dans RabbitMQ, la topologie est établie en instanciant exchanges et queues.
Les échanges sont en quelque sorte des routeurs, et les files d’attente leur sont liées – à l’aide d’abonnements à des clés de routage particulières – par les applications clientes pour stocker leurs messages en attente de traitement. Une bonne pratique consiste à considérer les files d’attente comme privées pour le service logique (mais partagées par les travailleurs de ce service logique). Nous utilisons le nom du type de message comme clé de routage.
La topologie est composée de trois échanges :
- un échange fiable événements avec :
- files d’attente persistantes pour les consommateurs (survivent aux redémarrages des courtiers et des services)
- persistent messages (survive broker and service restarts)
- message processing acknowledgment by clients
- message confirmation by broker (for early detection of network transient failures)
- topic routing (pour permettre la surveillance des caractères génériques)
- un échange fiable commandes avec :
- files d’attente persistantes pour les consommateurs (survivent aux redémarrages des courtiers et des services)
- messages persistants (survivent aux redémarrages des courtiers et des services)
- traitement des messages accusé de réception par les clients
- confirmation des messages par le courtier (pour la détection précoce des défaillances transitoires du réseau)
- topic routage
- a logs échange avec :
- pas de persistance (pour éviter d’inonder la mémoire du courtier au cas où personne ne consommerait les journaux)
- pas d’accusé de réception ou de confirmation du message (pour la vitesse)
- routage des lettres mortes : lorsqu’un message est refusé de façon permanente, il est automatiquement routé vers l’échange de lettres mortes.
Le résultat d’une commande est également envoyé à l’échange de commandes.
Les instances d’un même service logique sont appelées travailleurs de ce service, et elles partagent la même file d’attente. Le courtier garantit qu’un message est traité par un et un seul travailleur du pool attaché à la file d’attente.
L’accusé de réception du traitement des messages par les clients est un mécanisme très utile pour garantir qu’aucune donnée n’est perdue – c’est-à-dire qu’un message est garanti d’être traité au moins une fois – et pour permettre un équilibrage efficace de la charge. En effet, le courtier de messages ne retirera pas un message de la file d’attente tant que le travailleur qui l’a pris pour le traiter ne lui aura pas dit qu’il a terminé son travail. Pendant ce temps, le message est réservé. Si le travailleur qui a pris le message s’arrête avant d’accuser réception, le message devient disponible pour n’importe quel autre travailleur, ou pour le même travailleur lorsqu’il revient. En outre, le courtier sait à tout moment quels travailleurs sont occupés et lesquels sont inactifs, de sorte qu’il peut mieux répartir la charge entre eux.
Notez qu’il s’agit de la topologie de base. Comme la topologie est créée par les services eux-mêmes au lieu d’une configuration centrale, ils peuvent l’étendre localement (c’est-à-dire de leur côté) pour leurs propres besoins. Cela signifie également qu’ils doivent tous accepter la topologie de base exposée ci-dessus pour rejoindre le bus.
Comportements
La topologie décrit la manière dont le courtier achemine les messages et les garanties qu’il doit fournir.
L’AED exige également que les services mettent en œuvre certaines règles de base pour garantir la fiabilité et la performance :
- un message doit faire l’objet d’un accusé de réception une fois qu’il a été entièrement traité et jamais plus tôt ;
- le service doit conserver les messages envoyés dans le cache jusqu’à ce qu’ils soient acquittés par le courtier, et les renvoyer dans le cas contraire ;
- le service doit se reconnecter automatiquement au broker s’il est déconnecté, sans perdre son cache ;
- en cas d’échec inattendu lors du traitement d’un message, le service doit le relancer une fois avant de le rejeter définitivement ;
- le service doit détecter les erreurs irrécupérables (par exemple, les messages illégaux, les données incohérentes…), les enregistrer et accuser réception du message défectueux afin qu’il ne soit pas remis en file d’attente et qu’il ne soit pas acheminé vers les lettres mortes.le service doit détecter les erreurs irrécupérables (par exemple, les messages illégaux, les données incohérentes…), les enregistrer et accuser réception du message défectueux afin qu’il ne soit pas remis en file d’attente et qu’il ne soit pas acheminé vers les lettres mortes.
Cadre et outils
Pour éviter la duplication du code, nous avons développé des mini-frameworks (en Python, Elixir et Rust) qui implémentent cette topologie de base et tous les comportements requis des services. Nous en parlerons dans le prochain billet: Framework et outils !