Architecture Microservices : quel est le meilleur mode de communication entre Microservices ?

La base d’une architecture Microservices est de mettre en place de petites briques logicielles autonomes et interopérables, ce qui implique un fort besoin de communication entre elles.

Au-delà de la simple problématique de faire transiter des objets et des actions entre deux services, la communication interservices revêt une importance toute particulière dans ce type d’architecture. En effet, c’est d’elle que va dépendre l’agilité, l’évolutivité et la robustesse de la solution développée. Pour cela, il est indispensable de privilégier un axe orienté événements.

Microservices, plutôt synchrones ou asynchrones ?

Les échanges entre Microservices fonctionnent comme toute communication entre différents services et applications. En effet, le choix des armes est à la fois vaste et plutôt bien documenté. Je vous invite pour cela à consulter la liste des patterns d’intégration d’entreprise, ou EIP (voir https://www.enterpriseintegrationpatterns.com/).

En laissant de côté les solutions old school (telles que les transferts de fichiers ou les partages de base de données) qui ne correspondent pas à une architecture Microservices à l’état de l’art (lire à ce propos le livre blanc Nexworld : Les Microservices, c’est quoi ?), il nous reste les patterns classiques d’échange :

  • Appel synchrone point à point
  • Diffusion de messages asynchrones point à point
  • Diffusion d’événements

Ces patterns sont tous complémentaires entre eux et ont, d’une manière ou d’une autre, leur place dans les architectures Microservices que nous concevons. Cependant, le choix au cas par cas d’un mode de communication ou d’un autre, est loin d’être anodin et devrait être réalisé en connaissance de cause.

Comment éviter l’effet domino avec vos Microservices ?

Pour commencer, il s’agit d’un simple problème de dépendance. Si, par construction, mon service A utilise le service B pour réaliser un traitement, le traitement en question ne pourra être réalisé que si, et seulement si, A et B sont disponibles. Par déduction logique, le niveau de disponibilité et la vitesse d’exécution de mon traitement est inférieur ou égal au niveau de disponibilité du moins disponible de mes services A et B.

En poursuivant la logique, on se rend vite compte qu’un système complexe, où tous les services sont interdépendants, et dont cette dépendance est nécessaire pour la bonne réalisation du traitement, perdra progressivement en disponibilité au cours de l’accroissement de la complexité du système.

Supposons que le service le plus fragile du système soit utilisé par de nombreux traitements : c’est l’ensemble de ces traitements qui va se rendre indisponible, et potentiellement d’autres traitements dépendants d’eux également… et un terrible effet domino peut faire tomber toute votre plateforme Microservices, car tous les Microservices, indirectement, vont dépendre d’un même maillon faible.

 

Evolutions, comment préserver l’autonomie de vos services ?

Nous pouvons, déjà à ce stade, identifier une règle fondamentale des Microservices : un Microservice doit pouvoir réaliser ses traitements sans dépendre de la disponibilité de tout autre service.

Suivant le même principe de dépendance, c’est toute l’évolutivité du système qui va se réduire avec le temps, si par malheur la mise à jour d’un service devait entraîner la mise à jour simultanée d’un autre service.

Les interfaces des Microservices (qu’elles soient synchrones ou asynchrones) doivent donc être assez souples pour permettre l’enrichissement de leur contenu sans impacter les consommateurs du service.

En d’autres termes, si je décide d’ajouter un nouveau champ à mon service, le travail habituel consiste à ajouter ce champ à la fois dans le service d’exposition et dans le code des consommateurs de ce service.

À l’époque de SOAP, on aura fait évoluer le WSDL (contrat d’interface), regénérer le code d’exposition et de consommation du service à partir de ce WSDL, et préparer une mise en production simultanée des consommateurs et du service lui-même.

Ce genre de manipulation étant contraire au principe d’autonomie du Microservice, on implémentera nos composants de manière à pouvoir déployer producteurs et consommateurs de services à des moments différents, et dans n’importe quel ordre. Chacun des Microservices doit pouvoir conserver son propre cycle de vie.

Nous inscrivons donc ici une seconde règle fondamentale des Microservices : un Microservice doit faire évoluer ses interfaces sans perturber ses consommateurs actuels (et vice versa, un consommateur doit pouvoir être déployé en avance de phase par rapport au service qu’il consomme sans que cela ait de conséquence sur la bonne exécution de ses traitements).

Et si finalement le Bus d’événements était la meilleure solution ?

De ces règles découle l’une de nos convictions fortes : une bonne architecture Microservices doit avant tout reposer sur une logique de communication à base d’événements.

Pourquoi ce choix ? Parce que dans une architecture orientée événements, on a, par construction, une dépendance de chacun des services vers le bus d’événements et c’est tout. N’importe quel service peut tomber, aucun des autres services, tout comme le bus d’événements, n’en sera perturbé.

Évidemment, certains traitements n’auront pas lieu jusqu’à ce que les services indisponibles soient à nouveau disponibles, mais cette indisponibilité ne sera pas perceptible par les utilisateurs. Le corolaire de ce point, c’est que le bus d’événements doit avoir une disponibilité maximale, ce que le Cloud est heureusement capable de nous garantir.

Concernant l’évolutivité, il faut s’astreindre à faire évoluer le format des messages transitant sur le bus d’événements de façon rétro compatible, permettant d’ajouter progressivement de nouveaux champs sans perturber les consommateurs d’événements.

Pour une flexibilité maximale de vos développements et déploiements Microservices…

Pour conclure, articuler son architecture Microservices autour d’une architecture événements (voir la formation Nexworld Training : Event Driven Architecture), c’est garantir : la modularité, l’évolutivité et la stabilité de sa plateforme, en appliquant le concept de chorégraphie.

Chaque Microservice réagit et exécute ses traitements parce qu’il apprend qu’un événement a eu lieu (en consommant le message sur le bus d’événements) et qu’il est disponible pour le faire, non pas parce qu’on le sollicite directement. Le risque de panne par effet domino est ainsi considérablement réduit.

 

Par ailleurs, profitant des avantages du mode publish/subscribe), on peut facilement ajouter ou enlever un abonné sans perturber les autres Microservices. On peut en outre se permettre de déployer les futurs consommateurs plusieurs jours avant le producteur de l’événement, garantissant une flexibilité maximale du déploiement et de la gestion des équipes de développement.

Auteur : onepoint

beyond the obvious