Chose promise, chose due ! Alors même que notre rédacteur en chef a pris des vacances bien méritées, les articles continuent de paraître. Il y a quelques semaines, j’avais tenté de mettre en lumière deux approches de conception d’architectures distribuées pour des applications d’entreprise qu’elles soient ouvertes sur le Web ou non. Après avoir présenté le modèle d’acteur et l’avoir comparé aux microservices, il est maintenant temps de rentrer dans le vif du sujet des problèmes de conception concrets que cela induit.
Si vous vous souvenez, mon idée de base était de conjuguer acteurs et services au sein de la même architecture, les services prenant en compte la responsabilité de présentation de l’API et les acteurs celle de la réalisation de chaque fonction offerte par cette API. Dans la conclusion de cet article, j’en étais venu à affirmer qu’il n’y avait finalement que peu de différences entre un acteur et un microservice du moment que l’on supprimait à l’acteur son pouvoir de changer son comportement en fonction d’un message (ce que, formellement, un acteur doit être capable de faire). Nous allons voir que cet aspect, s’il est conservé, pourrait avoir un avantage en termes de conception.
Dans une architecture de micro-services, il existe un problème d’adaptation bien documenté : comment les clients des microservices accèdent individuellement à ces derniers ? Le problème prend de l’ampleur lorsque des clients de nature fort différente utilisent les même services. Par exemple, une application web et une app mobile ont des besoins en données différents et des capacités d’affichage différents, l’une et l’autre ne peuvent finalement pas utiliser le même service et, pour autant, il est contre-productif de leur associer deux services différents. Dans le même ordre d’idées, les microservices offrent une granularité très fine qui peut être un frein pour certaines applications. Enfin, le schéma de déploiement des micro-services est dynamique, l’adresse de leur hôte l’est aussi et ça, il faut que ce soit transparent aux clients.
La solution est un pattern connu sous le nom d’API Gateway qui est une évolution du pattern Facade. L’API Gateway cache aux client la complexité du schéma de déploiement des services, sert de proxy et route les requêtes/réponses en assurant, le cas échéant, des tâches de formatage. Dans certains cas de requêtes complexes faisant appel à plusieurs micro-services, l’API Gateway prend en charge l’orchestration des appels que cette requête occasionne.
Cependant, en résolvant tous ces problèmes, ce pattern peut évoluer en un anti-pattern. En effet, il s’agit tout d’abord d’un point d’entrée unique vers le système de services et, de fait, peut aisément se transformer en SpoF (Single Point of Failure). Ensuite, ajouter des responsabilité de routage, d’orchestration, des aspects de sécurité et de traduction de format au sein d’un même composant revient à introduire l’esprit et la philosophie du pattern Monolithe au sein d’une architecture de microservices ce qui est , vous en conviendrez, à l’opposé de l’objectif initial.
Il est tout de même important de considérer que le recours à API Gateway est absolument essentiel dès que vous avez une application mobile car des appels successifs à différents services pour traiter une requête imposent trop de charge au réseau mobile sans parler des requêtes qui n’aboutissent pas.
Lorsque les microservices sont remplacés par des acteurs, le problème pourrait se simplifier. Quelles sont finalement les responsabilités incontournables du pattern API Gateway ? Le découplage localisation/accès et l’orchestration. A contrario, présenter une API optimale pour chaque agent utilisateur peut justement être la responsabilité d’un service spécifique mais ne représente pas, à mon avis, l’essence même du pattern.
Dans ces conditions, si on accepte qu’un acteur puisse modifier son comportement à la réception d’un message, on peut imaginer le scénario suivant :
1. Un client émet une requête HTTP Rest via une API de service toute bête (une simple couche de présentation côté serveur). 2. L’API demande au registre d’acteurs un acteur disponible, la requête atteint cet acteur sous forme de message. 3. L’acteur ne sait pas traiter la requête et demande à un acteur d’administration la conduite à tenir. 4. L’acteur d’administration envoie à l’acteur du code (Code On Demand) afin de modifier son comportement et être capable de traiter la requête (souvent en la routant vers l’acteur spécialisé) 5. L’acteur ré-émet lui-même le message initial et y répond grâce au code reçu de l’acteur d’administration.
Ce scénario prévoit des rôles particuliers : un registre d’acteurs et un acteur d’administration. On peut considérer des composants qui seront aussi des SPoF mais, étant des acteurs, il est possible de les rendre redondants et ainsi éviter ce funeste statut. Le registre d’acteurs est simplement un annuaire global ou local des acteurs logiciels assortis de leur localisation (adresse et port), l’aspect localisation/accès est de sa responsabilité. L’acteur d’administration est un acteur spécifique auquel s’adressent les autres dès qu’une exception survient. Bien souvent, il ne s’agira que d’un rapport d’incident mais il est beaucoup plus élégant et fructueux de produire dans ce cas du Code On Demand afin de modifier dynamiquement le comportement des acteurs. On peut également imaginer que cet acteur puisse envoyer le code des appels successifs pour des requêtes complexes sur requête d’un acteur et ainsi adresser l’aspect orchestration.
Cette solution basée sur des acteurs spécifiques est plus homogène au modèle sous-jacent que ne l’est la solution API Gateway car elle ne peut pas, par conception, évoluer accidentellement vers un anti-pattern (God Object ou Unwanted Monolith). Cependant, il est nécessaire de limiter les sollicitation de Code On Demand aux cas les plus complexes et il est, bien sûr, nécessaire d’utiliser un langage dynamique ou de disposer d’un interpréteur embarqué afin de pouvoir utiliser cette technique.
Ces technologies vous intéressent ? Vous souhaitez aller plus loin sur ces sujets ?