Rédacteur – Cette série d’articles en sept parties est désormais terminée :
Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement . Consultez également la nouvelle page Solutions Microservices .
Il s'agit du troisième article de notre série sur la création d'applications avec une architecture de microservices. Le premier article présente le modèle d’architecture de microservices , le compare au modèle d’architecture monolithique et discute des avantages et des inconvénients de l’utilisation de microservices. Le deuxième article décrit comment les clients d’une application communiquent avec les microservices via un intermédiaire appelé passerelle API . Dans cet article, nous examinons comment les services d’un système communiquent entre eux. Le quatrième article explore le problème étroitement lié de la découverte de services.
Dans une application monolithique, les composants s’invoquent les uns les autres via des appels de méthodes ou de fonctions au niveau du langage. En revanche, une application basée sur des microservices est un système distribué exécuté sur plusieurs machines. Chaque instance de service est généralement un processus. Par conséquent, comme le montre le diagramme suivant, les services doivent interagir à l’aide d’un mécanisme de communication interprocessus (IPC).
Nous examinerons plus tard des technologies IPC spécifiques, mais explorons d’abord divers problèmes de conception.
Lors de la sélection d’un mécanisme IPC pour un service, il est utile de réfléchir d’abord à la manière dont les services interagissent. Il existe une variété de styles d’interaction client⇔service. Ils peuvent être classés selon deux dimensions. La première dimension est de savoir si l’interaction est de type un-à-un ou un-à-plusieurs :
La deuxième dimension est de savoir si l’interaction est synchrone ou asynchrone :
Le tableau suivant présente les différents styles d’interaction.
Un à un | Un-à-plusieurs | |
---|---|---|
Synchrone | Demande/réponse | — |
Asynchrone | Notification | Publier/s'abonner |
Requête/réponse asynchrone | Publier/réponses asynchrones |
Il existe les types d’interactions individuelles suivants :
Il existe les types d’interactions un-à-plusieurs suivants :
Chaque service utilise généralement une combinaison de ces styles d’interaction. Pour certains services, un seul mécanisme IPC est suffisant. D’autres services pourraient avoir besoin d’utiliser une combinaison de mécanismes IPC. Le diagramme suivant montre comment les services d'une application de commande de taxi peuvent interagir lorsque l'utilisateur demande un trajet.
Les services utilisent une combinaison de notifications, de demande/réponse et de publication/abonnement. Par exemple, le smartphone du passager envoie une notification au service de gestion de voyage pour demander une prise en charge. Le service de gestion de voyage vérifie que le compte du passager est actif en utilisant une requête/réponse pour invoquer le service passager. Le service de gestion de voyage crée ensuite le voyage et utilise la publication/abonnement pour notifier d'autres services, y compris le répartiteur, qui localise un chauffeur disponible.
Maintenant que nous avons examiné les styles d’interaction, voyons comment définir les API.
L'API d'un service est un contrat entre le service et ses clients. Quel que soit votre choix de mécanisme IPC, il est important de définir précisément l'API d'un service à l'aide d'une sorte de langage de définition d'interface (IDL). Il existe même de bons arguments en faveur de l’utilisation d’une approche API-first pour définir les services. Vous commencez le développement d’un service en écrivant la définition de l’interface et en la révisant avec les développeurs clients. Ce n’est qu’après avoir itéré sur la définition de l’API que vous implémentez le service. Réaliser cette conception en amont augmente vos chances de créer un service qui répond aux besoins de vos clients.
Comme vous le verrez plus loin dans cet article, la nature de la définition de l’API dépend du mécanisme IPC que vous utilisez. Si vous utilisez la messagerie, l'API se compose des canaux de messages et des types de messages. Si vous utilisez HTTP, l'API se compose des URL et des formats de demande et de réponse. Nous décrirons plus en détail certains IDL plus tard.
L'API d'un service change invariablement au fil du temps. Dans une application monolithique, il est généralement simple de modifier l'API et de mettre à jour tous les appelants. Dans une application basée sur des microservices, c’est beaucoup plus difficile, même si tous les consommateurs de votre API sont d’autres services de la même application. En règle générale, vous ne pouvez pas forcer tous les clients à effectuer une mise à niveau en même temps que le service. De plus, vous déploierez probablement progressivement de nouvelles versions d’un service de sorte que les anciennes et les nouvelles versions d’un service s’exécuteront simultanément. Il est important d’avoir une stratégie pour faire face à ces problèmes.
La manière dont vous gérez un changement d’API dépend de la taille du changement. Certaines modifications sont mineures et rétrocompatibles avec la version précédente. Vous pouvez, par exemple, ajouter des attributs aux demandes ou aux réponses. Il est logique de concevoir les clients et les services de manière à ce qu’ils respectent le principe de robustesse . Les clients qui utilisent une ancienne API doivent continuer à travailler avec la nouvelle version du service. Le service fournit des valeurs par défaut pour les attributs de demande manquants et les clients ignorent tous les attributs de réponse supplémentaires. Il est important d'utiliser un mécanisme IPC et un format de messagerie qui vous permettent de faire évoluer facilement vos API.
Il arrive cependant que vous deviez apporter des modifications majeures et incompatibles à une API. Comme vous ne pouvez pas forcer les clients à effectuer une mise à niveau immédiate, un service doit prendre en charge les anciennes versions de l'API pendant un certain temps. Si vous utilisez un mécanisme basé sur HTTP tel que REST, une approche consiste à intégrer le numéro de version dans l’URL. Chaque instance de service peut gérer plusieurs versions simultanément. Vous pouvez également déployer différentes instances gérant chacune une version particulière.
Considérez, par exemple, le scénario Détails du produit de cet article. Imaginons que le service de recommandation ne réponde pas. Une implémentation naïve d’un client pourrait se bloquer indéfiniment en attendant une réponse. Non seulement cela entraînerait une mauvaise expérience utilisateur, mais dans de nombreuses applications, cela consommerait une ressource précieuse telle qu'un thread. Finalement, le runtime manque de threads et cesse de répondre, comme le montre la figure suivante.
Pour éviter ce problème, il est essentiel de concevoir vos services pour gérer les pannes partielles.
Une bonne approche à suivre est celle décrite par Netflix . Les stratégies pour faire face aux défaillances partielles comprennent :
Netflix Hystrix est une bibliothèque open source qui implémente ces modèles et d'autres. Si vous utilisez la JVM, vous devriez certainement envisager d'utiliser Hystrix. Et si vous travaillez dans un environnement non JVM, vous devez utiliser une bibliothèque équivalente.
Il existe de nombreuses technologies IPC différentes parmi lesquelles choisir. Les services peuvent utiliser des mécanismes de communication synchrones basés sur des requêtes/réponses tels que REST basé sur HTTP ou Thrift. Alternativement, ils peuvent utiliser des mécanismes de communication asynchrones basés sur des messages tels que AMQP ou STOMP. Il existe également une variété de formats de messages différents. Les services peuvent utiliser des formats textuels lisibles par l’homme tels que JSON ou XML. Alternativement, ils peuvent utiliser un format binaire (qui est plus efficace) tel qu'Avro ou Protocol Buffers. Nous examinerons plus tard les mécanismes IPC synchrones, mais discutons d’abord des mécanismes IPC asynchrones.
Lors de l'utilisation de la messagerie, les processus communiquent en échangeant des messages de manière asynchrone. Un client fait une demande à un service en lui envoyant un message. Si le service doit répondre, il le fait en renvoyant un message séparé au client. Comme la communication est asynchrone, le client ne se bloque pas en attendant une réponse. Au lieu de cela, le client est informé que la réponse ne sera pas reçue immédiatement.
Un message se compose d'en-têtes (métadonnées telles que l'expéditeur) et d'un corps de message. Les messages sont échangés via des canaux . N'importe quel nombre de producteurs peut envoyer des messages à un canal. De même, n’importe quel nombre de consommateurs peut recevoir des messages d’un canal. Il existe deux types de canaux : point à point et publication-abonnement . Un canal point à point délivre un message à exactement l’un des consommateurs qui lit le canal. Les services utilisent des canaux point à point pour les styles d’interaction un à un décrits précédemment. Un canal de publication-abonnement délivre chaque message à tous les consommateurs attachés. Les services utilisent des canaux de publication-abonnement pour les styles d’interaction un-à-plusieurs décrits ci-dessus.
Le diagramme suivant montre comment l’application de réservation de taxi peut utiliser les canaux de publication-abonnement.
Le service de gestion de voyage informe les services intéressés, tels que le répartiteur, d'un nouveau voyage en écrivant un message de voyage créé sur un canal de publication-abonnement. Le répartiteur trouve un pilote disponible et avertit les autres services en écrivant un message de proposition de pilote sur un canal de publication-abonnement.
Il existe de nombreux systèmes de messagerie parmi lesquels choisir. Vous devez en choisir un qui prend en charge une variété de langages de programmation. Certains systèmes de messagerie prennent en charge des protocoles standards tels que AMQP et STOMP. D’autres systèmes de messagerie ont des protocoles propriétaires mais documentés. Il existe un grand nombre de systèmes de messagerie open source parmi lesquels choisir, notamment RabbitMQ , Apache Kafka , Apache ActiveMQ et NSQ . À un niveau élevé, ils prennent tous en charge une certaine forme de messages et de canaux. Ils s’efforcent tous d’être fiables, performants et évolutifs. Il existe cependant des différences significatives dans les détails du modèle de messagerie de chaque courtier.
L’utilisation de la messagerie présente de nombreux avantages :
L’utilisation de la messagerie présente cependant certains inconvénients :
Maintenant que nous avons vu l’utilisation de l’IPC basé sur la messagerie, examinons l’IPC basé sur les requêtes/réponses.
Lors de l'utilisation d'un mécanisme IPC synchrone basé sur une demande/réponse, un client envoie une demande à un service. Le service traite la demande et renvoie une réponse. Dans de nombreux clients, le thread qui effectue la requête se bloque en attendant une réponse. D’autres clients peuvent utiliser du code client asynchrone, piloté par événements, qui est peut-être encapsulé par Futures ou Rx Observables. Cependant, contrairement à l’utilisation de la messagerie, le client suppose que la réponse arrivera en temps opportun. Il existe de nombreux protocoles parmi lesquels choisir. REST et Thrift sont deux protocoles populaires. Commençons d’abord par examiner REST.
Aujourd’hui, il est à la mode de développer des API dans le style RESTful . REST est un mécanisme IPC qui utilise (presque toujours) HTTP. Un concept clé dans REST est une ressource, qui représente généralement un objet métier tel qu'un client ou un produit, ou une collection d'objets métier. REST utilise les verbes HTTP pour manipuler les ressources, qui sont référencées à l'aide d'une URL. Par exemple, une requête GET
renvoie la représentation d'une ressource, qui peut se présenter sous la forme d'un document XML ou d'un objet JSON. Une requête POST
crée une nouvelle ressource et une requête PUT
met à jour une ressource. Pour citer Roy Fielding, le créateur de REST :
Le diagramme suivant montre l’une des façons dont l’application de commande de taxi peut utiliser REST.
Le smartphone du passager demande un voyage en effectuant une requête POST
à la ressource /trips
du service Trip Management. Ce service gère la demande en envoyant une requête GET
pour obtenir des informations sur le passager au service de gestion des passagers. Après avoir vérifié que le passager est autorisé à créer un voyage, le service de gestion de voyage crée le voyage et renvoie un201
réponse au smartphone.
De nombreux développeurs affirment que leurs API basées sur HTTP sont RESTful. Cependant, comme le décrit Fielding dans cet article de blog , ce n’est pas le cas de tous. Leonard Richardson (sans lien de parenté) définit un modèle de maturité très utile pour REST qui se compose des niveaux suivants.
POST
vers son seul point de terminaison URL. Chaque requête spécifie l'action à effectuer, la cible de l'action (par exemple l'objet métier) et les éventuels paramètres.POST
qui spécifie l'action à effectuer et tous les paramètres.GET
pour récupérer, POST
pour créer et PUT
pour mettre à jour. Les paramètres et le corps de la requête, le cas échéant, spécifient les paramètres de l'action. Cela permet aux services d'exploiter l'infrastructure Web telle que la mise en cache des requêtes GET
.GET
contient des liens permettant d’effectuer les actions autorisées sur cette ressource. Par exemple, un client peut annuler une commande en utilisant un lien dans la représentation de commande renvoyée en réponse à la requête GET
envoyée pour récupérer la commande. Les avantages de HATEOAS incluent le fait de ne plus avoir à intégrer les URL dans le code client. Un autre avantage est que, comme la représentation d’une ressource contient des liens vers les actions autorisées, le client n’a pas à deviner quelles actions peuvent être effectuées sur une ressource dans son état actuel.L’utilisation d’un protocole basé sur HTTP présente de nombreux avantages :
curl
(en supposant que JSON ou un autre format de texte soit utilisé).L'utilisation de HTTP présente certains inconvénients :
La communauté des développeurs a récemment redécouvert la valeur d’un langage de définition d’interface pour les API RESTful. Il existe quelques options, notamment RAML et Swagger . Certains IDL tels que Swagger vous permettent de définir le format des messages de demande et de réponse. D'autres, comme RAML, nécessitent l'utilisation d'une spécification distincte telle que JSON Schema . En plus de décrire les API, les IDL disposent généralement d'outils qui génèrent des stubs clients et des squelettes de serveurs à partir d'une définition d'interface.
Apache Thrift est une alternative intéressante à REST. Il s'agit d'un framework permettant d'écrire des clients et des serveurs RPC multilingues. Thrift fournit un IDL de style C pour définir vos API. Vous utilisez le compilateur Thrift pour générer des stubs côté client et des squelettes côté serveur. Le compilateur génère du code pour une variété de langages, notamment C++, Java, Python, PHP, Ruby, Erlang et Node.js.
Une interface Thrift se compose d'un ou plusieurs services. Une définition de service est analogue à une interface Java. Il s'agit d'un ensemble de méthodes fortement typées. Les méthodes d’épargne peuvent soit renvoyer une valeur (éventuellement nulle), soit être définies comme unidirectionnelles. Les méthodes qui renvoient une valeur implémentent le style d’interaction demande/réponse. Le client attend une réponse et peut générer une exception. Les méthodes unidirectionnelles correspondent au style de notification de l’interaction. Le serveur n'envoie pas de réponse.
Thrift prend en charge différents formats de messages : JSON, binaire et binaire compact. Le binaire est plus efficace que JSON car il est plus rapide à décoder. Et, comme son nom l’indique, le binaire compact est un format peu encombrant. JSON est, bien entendu, convivial pour les humains et les navigateurs. Thrift vous offre également un choix de protocoles de transport, notamment TCP brut et HTTP. Le TCP brut est probablement plus efficace que HTTP. Cependant, HTTP est compatible avec le pare-feu, le navigateur et l'utilisateur.
Maintenant que nous avons examiné HTTP et Thrift, examinons la question des formats de message. Si vous utilisez un système de messagerie ou REST, vous pouvez choisir le format de votre message. D'autres mécanismes IPC tels que Thrift peuvent ne prendre en charge qu'un petit nombre de formats de messages, peut-être un seul. Dans les deux cas, il est important d’utiliser un format de message multilingue. Même si vous écrivez vos microservices dans un seul langage aujourd’hui, il est probable que vous utiliserez d’autres langages à l’avenir.
Il existe deux principaux types de formats de messages : texte et binaire. Les exemples de formats basés sur du texte incluent JSON et XML. L’un des avantages de ces formats est qu’ils sont non seulement lisibles par l’homme, mais qu’ils sont également autodescriptifs. Dans JSON, les attributs d’un objet sont représentés par une collection de paires nom-valeur. De même, dans XML, les attributs sont représentés par des éléments nommés et des valeurs. Cela permet au consommateur d’un message de sélectionner les valeurs qui l’intéressent et d’ignorer le reste. Par conséquent, des modifications mineures apportées au format du message peuvent être facilement rétrocompatibles.
La structure des documents XML est spécifiée par un schéma XML . Au fil du temps, la communauté des développeurs s’est rendu compte que JSON avait également besoin d’un mécanisme similaire. Une option consiste à utiliser le schéma JSON , soit de manière autonome, soit dans le cadre d’un IDL tel que Swagger.
L’inconvénient de l’utilisation d’un format de message basé sur du texte est que les messages ont tendance à être verbeux, en particulier XML. Étant donné que les messages sont autodescriptifs, chaque message contient le nom des attributs en plus de leurs valeurs. Un autre inconvénient est la surcharge liée à l’analyse du texte. Par conséquent, vous pourriez envisager d’utiliser un format binaire.
Il existe plusieurs formats binaires parmi lesquels choisir. Si vous utilisez Thrift RPC, vous pouvez utiliser Thrift binaire. Si vous devez choisir le format du message, les options les plus populaires incluent Protocol Buffers et Apache Avro . Ces deux formats fournissent un IDL typé pour définir la structure de vos messages. Une différence cependant est que les tampons de protocole utilisent des champs balisés, alors qu'un consommateur Avro doit connaître le schéma pour interpréter les messages. Par conséquent, l’évolution des API est plus facile avec Protocol Buffers qu’avec Avro. Cet article de blog est une excellente comparaison de Thrift, Protocol Buffers et Avro.
Les microservices doivent communiquer à l’aide d’un mécanisme de communication interprocessus. Lors de la conception de la manière dont vos services communiqueront, vous devez prendre en compte divers problèmes : comment les services interagissent, comment spécifier l'API pour chaque service, comment faire évoluer les API et comment gérer les échecs partiels. Il existe deux types de mécanismes IPC que les microservices peuvent utiliser : la messagerie asynchrone et la requête/réponse synchrone. Dans le prochain article de la série, nous examinerons le problème de la découverte de services dans une architecture de microservices.
Rédacteur – Cette série d’articles en sept parties est désormais terminée :
Vous pouvez également télécharger l'ensemble complet des articles, ainsi que des informations sur la mise en œuvre de microservices à l'aide de NGINX Plus, sous forme d'ebook – Microservices : De la conception au déploiement .
Le blogueur invité Chris Richardson est le fondateur du CloudFoundry.com original, un PaaS (Platform as a Service) Java précoce pour Amazon EC2. Il conseille désormais les organisations pour améliorer la manière dont elles développent et déploient des applications. Il blogue également régulièrement sur les microservices sur http://microservices.io .
« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."