Contrats, adressage et API pour les microservices

Sur App Engine, les microservices s'appellent généralement les uns les autres à l'aide d'API RESTful basées sur HTTP. Il est également possible d'appeler des microservices en arrière-plan à l'aide de files d'attente de tâches, et les principes de conception d'API décrits ici s'appliquent. Il est important que vous suiviez certains modèles afin de vous assurer que votre application basée sur des microservices est stable, sécurisée et performante.

Utiliser des contrats forts

L'un des aspects les plus importants des applications basées sur des microservices est la possibilité de déployer des microservices entièrement indépendants les uns des autres. Pour que cette indépendance soit possible, chaque microservice doit fournir à ses clients, qui sont d'autres microservices, un contrat bien défini et avec versions gérées. Un service ne doit pas rompre ces contrats avec versions gérées avant qu'il soit établi qu'aucun autre microservice ne repose sur un contrat spécifique avec versions gérées. N'oubliez pas que d'autres microservices peuvent avoir besoin d'effectuer un rollback vers une version de code antérieure nécessitant un contrat précédent. Il est donc important de tenir compte de ce facteur dans vos règles d'abandon et de désactivation.

Une culture basée sur des contrats forts avec versions gérées est probablement l'aspect organisationnel le plus complexe d'une application stable basée sur des microservices. Les équipes de développement doivent assimiler le concept de modification destructive par rapport à une modification non destructive. Elles doivent savoir à quel moment une nouvelle version majeure est requise, ainsi que comprendre comment et quand un ancien contrat peut être mis hors service. Les équipes doivent employer les techniques de communication appropriées, y compris les avis d'abandon et de désactivation, pour assurer la diffusion des informations sur les modifications apportées aux contrats de microservice. Bien que cela puisse sembler une tâche ardue, l'intégration de ces pratiques dans votre culture de développement apportera d'importantes améliorations en matière de vélocité et de qualité au fil du temps.

Adresser les microservices

Les services et les versions de code peuvent être directement adressés. Par conséquent, vous pouvez déployer les nouvelles versions de code et les versions de code existantes côte à côte. Vous pouvez également tester le nouveau code avant de vous en servir pour créer une version de diffusion par défaut.

Chaque projet App Engine comporte un service par défaut et chaque service comporte une version de code par défaut. Pour adresser le service par défaut de la version par défaut d'un projet ayant l'ID d'application my-app, utilisez l'URL suivante :

http://my-app.appspot.com

Si vous déployez un service nommé user-service, vous pouvez accéder à la version de diffusion par défaut de ce dernier à l'aide de l'URL suivante :

http://user-service.my-app.appspot.com

Si vous déployez une seconde version de code autre que celle par défaut, nommée banana, sur le service user-service, vous pouvez accéder directement à cette version de code à l'aide de l'URL suivante :

http://banana.user-service.my-app.appspot.com

Notez que si vous déployez une seconde version de code autre que celle par défaut, nommée cherry, sur le service default, vous pouvez accéder à cette version de code à l'aide de l'URL suivante :

http://cherry.my-app.appspot.com

App Engine applique la règle selon laquelle les noms des versions de code dans le service par défaut ne peuvent pas entrer en conflit avec les noms de services.

Les versions de code spécifiques à adressage direct ne doivent être employées que pour effectuer un test de confiance et pour faciliter l'exécution de tests A/B, de restaurations par progression et de rollbacks. À la place, votre code client ne doit adresser que la version de diffusion par défaut du service par défaut ou d'un service spécifique :

http://my-app.appspot.com
http://user-service.my-app.appspot.com

Ce style d'adressage permet aux microservices de déployer de nouvelles versions de leurs services, y compris des corrections de bugs, sans que des modifications des clients ne soient nécessaires.

Tous les noms d'hôte mentionnés ci-dessus disposent d'équivalents HTTPS. Le certificat intégré d'App Engine sur appspot.com n'est pas compatible avec les noms d'hôte profonds des services et des versions. Par conséquent, les noms doivent être aplatis à l'aide de -dot-. Par exemple, les noms d'hôte suivants sont les équivalents des exemples précédents, mais modifiés pour HTTPS :

https://my-app.appspot.com
https://user-service-dot-my-app.appspot.com
https://banana-dot-user-service-dot-my-app.appspot.com  # whew, that's a mouthful
https://cherry-dot-my-app.appspot.com

Utiliser des versions d'API

Une version majeure doit figurer dans l'URL de toutes les API de microservices, par exemple :

/user-service/v1/

Cette version d'API majeure identifie clairement dans les journaux la version d'API du microservice qui est appelée. Plus important encore, la version d'API majeure génère différentes URL, de sorte que les nouvelles et les anciennes versions d'API majeures peuvent être diffusées côte à côte :

/user-service/v1/
/user-service/v2/

Il n'est pas nécessaire d'inclure la version d'API mineure dans l'URL, car ce type de version n'engendre aucune modification destructive par définition. En fait, son inclusion dans l'URL entraînerait une prolifération d'URL et susciterait des incertitudes quant à la capacité d'un client à passer à une nouvelle version d'API mineure.

Dans cet article, nous partons du principe que vous disposez d'un environnement d'intégration et de diffusion continues dans lequel la branche principale est toujours déployée sur App Engine. Deux concepts distincts de version sont évoqués dans cet article :

  • La version de code, qui est directement associée à une version du service App Engine et représente un tag de commit particulier de la branche maître

  • La version d'API, qui est directement associée à une URL d'API et représente la forme des arguments de la requête, la forme du document de réponse et le comportement de l'API

Dans cet article, nous supposons également qu'un seul déploiement de code mettra en œuvre les anciennes et les nouvelles versions d'une API dans une version de code commune. Par exemple, la branche principale déployée peut mettre en œuvre à la fois /user-service/v1/ et /user-service/v2/. Lors du déploiement de nouvelles versions mineures et correctives, cette approche vous permet de répartir le trafic entre deux versions de code indépendamment des versions d'API réellement mises en œuvre par le code.

Votre organisation peut choisir de développer les versions /user-service/v1/ et /user-service/v2/ sur différentes branches de code, ce qui signifie qu'aucun déploiement de code ne les mettra en œuvre en même temps. Ce modèle est également possible sur App Engine, mais pour répartir le trafic, vous devrez déplacer la version d'API majeure dans le nom du service lui-même. Par exemple, vos clients utiliseront les URL suivantes :

http://user-service-v1.my-app.appspot.com/user-service/v1/
http://user-service-v2.my-app.appspot.com/user-service/v2/

La version d'API majeure est déplacée dans le nom du service lui-même, par exemple user-service-v1 et user-service-v2. (Les parties /v1/, /v2/ du chemin d'accès sont redondantes dans ce modèle et pourraient être supprimées, même si elles peuvent toujours être utiles pour l'analyse des journaux.) Ce modèle implique un peu plus de travail, car vous devrez probablement mettre à jour les scripts de déploiement pour déployer de nouveaux services lors de modifications de la version d'API majeure. Notez également le nombre maximal de services autorisés par application App Engine.

Modifications destructives et non destructives

Il est important de bien comprendre la différence entre une modification destructive et une modification non destructive. Les modifications destructives sont souvent soustractives, ce qui signifie qu'elles suppriment une partie du document de requête ou de réponse. Le changement de la forme du document ou du nom des clés peut entraîner une modification destructive. De nouveaux arguments requis constituent toujours des modifications destructives. Des modifications destructives peuvent également se produire si le comportement du microservice change.

Les modifications non destructives ont tendance à être additives. L'ajout d'un argument de requête facultatif ou d'une section supplémentaire dans le document de réponse donne lieu à des modifications non destructives. Afin de réaliser ces modifications, le type de sérialisation sur le réseau choisi est essentiel. De nombreuses sérialisations sont compatibles avec les modifications non destructives : JSON, Protocol Buffers ou Thrift. Lorsqu'elles sont désérialisées, ces sérialisations ignorent en mode silencieux les informations supplémentaires inattendues. Dans les langages dynamiques, les informations supplémentaires apparaissent simplement dans l'objet désérialisé.

Examinez la définition JSON suivante pour le service /user-service/v1/ :

{
  "userId": "UID-123",
  "firstName": "Jake",
  "lastName": "Cole",
  "username": "jcole@example.com"
}

La modification destructive ci-dessous nécessiterait une nouvelle gestion des versions du service en tant que /user-service/v2/ :

{
  "userId": "UID-123",
  "name": "Jake Cole",  # combined fields
  "email": "jcole@example.com"  # key change
}

Toutefois, la modification non destructive suivante ne nécessite pas de nouvelle version :

{
  "userId": "UID-123",
  "firstName": "Jake",
  "lastName": "Cole",
  "username": "jcole@example.com",
  "company": "Acme Corp."  # new key
}

Déployer de nouvelles versions d'API mineures non destructives

Lors du déploiement d'une nouvelle version d'API mineure, App Engine autorise la publication de la nouvelle version et de l'ancienne version de code côte à côte. Sur App Engine, bien que vous puissiez adresser directement l'une des versions déployées, une seule version représente la version de diffusion par défaut. Rappelons qu'il existe une version de diffusion par défaut pour chaque service. Dans cet exemple, notre ancienne version de code, nommée apple, se trouve être la version de diffusion par défaut et nous déployons la nouvelle version de code côte à côte, nommée banana. Notez que les URL de microservice de ces deux versions sont identiques (/user-service/v1/), puisque nous déployons une modification d'API mineure non destructive.

App Engine comporte des mécanismes permettant de migrer automatiquement le trafic de apple vers banana en marquant la nouvelle version de code banana en tant que version de diffusion par défaut. Lorsque la nouvelle version de diffusion par défaut est définie, aucune nouvelle requête n'est acheminée vers apple et toutes les nouvelles requêtes sont dirigées vers banana. C'est ainsi que vous effectuez une restauration par progression vers une nouvelle version de code qui met en œuvre une nouvelle version d'API mineure ou corrective, sans aucune incidence sur les microservices clients.

En cas d'erreur, vous pouvez effectuer un rollback en inversant le processus ci-dessus : rétablissez l'ancienne version de diffusion par défaut, apple dans notre exemple. Toutes les nouvelles requêtes seront réacheminées vers l'ancienne version de code et aucune nouvelle requête ne sera dirigée vers banana. Notez que vous pouvez terminer les requêtes en cours.

App Engine offre également la possibilité de ne diriger qu'un certain pourcentage du trafic vers la nouvelle version de code. Ce processus est souvent appelé processus de déploiement Canary et le mécanisme est nommé répartition du trafic dans App Engine. Vous pouvez diriger 1 %, 10 %, 50 % ou n'importe quel pourcentage du trafic souhaité vers les nouvelles versions de code ; vous avez également la possibilité d'ajuster ce volume par la suite. Par exemple, vous pouvez déployer votre nouvelle version de code pendant 15 minutes, en augmentant lentement le trafic et en surveillant tout problème susceptible d'indiquer qu'un rollback est nécessaire. Ce même mécanisme vous permet d'exécuter un test A/B sur deux versions de code : définissez la répartition du trafic sur 50 %, puis comparez les caractéristiques de performances et de taux d'erreur des deux versions de code pour confirmer les améliorations attendues.

L'illustration suivante montre les paramètres de répartition du trafic dans la console GCP :

Paramètres de répartition du trafic dans la console Google Cloud Platform

Déployer de nouvelles versions d'API majeures destructives

Lorsque vous déployez des versions d'API majeures destructives, le processus de restauration par progression et de rollback est identique à celui des versions d'API mineures non destructives. Toutefois, vous n'effectuerez généralement aucun test A/B ni aucune répartition du trafic, car la version d'API destructive est une URL récemment publiée, telle que /user-service/v2/. Bien sûr, si vous avez modifié la mise en œuvre sous-jacente de votre ancienne version d'API majeure, vous pouvez toujours vérifier que cette dernière continue de fonctionner comme prévu en faisant appel à la répartition du trafic.

Lors du déploiement d'une nouvelle version d'API majeure, il est important de garder à l'esprit que les anciennes versions d'API majeures peuvent être toujours en cours de diffusion. Par exemple, la version /user-service/v1/ peut être toujours diffusée lorsque la version /user-service/v2/ est publiée. Cet aspect constitue une partie essentielle des versions de code indépendantes. Vous ne pouvez désactiver les anciennes versions d'API majeures qu'après avoir vérifié qu'aucun autre microservice ne les requiert, y compris d'autres microservices pouvant nécessiter un rollback vers une version de code plus ancienne.

Pour prendre un exemple concret, imaginons que vous disposiez du microservice web-app qui dépend d'un autre microservice, nommé user-service. Imaginez que user-service doive modifier la mise en œuvre sous-jacente, ce qui rendra incompatible l'ancienne version d'API majeure employée actuellement par web-app, par exemple pour réduire firstName et lastName à un seul champ nommé name. En d'autres termes, user-service doit désactiver une ancienne version d'API majeure.

Pour que ce changement puisse être appliqué, trois déploiements distincts doivent être effectués :

  • Tout d'abord, user-service doit déployer /user-service/v2/ tout en continuant à être compatible avec /user-service/v1/. Ce déploiement peut nécessiter l'écriture de code temporaire pour permettre la rétrocompatibilité, ce qui est une conséquence courante dans les applications basées sur des microservices.

  • Ensuite, web-app doit déployer le code mis à jour qui fait passer sa dépendance de /user-service/v1/ à /user-service/v2/.

  • Enfin, une fois que l'équipe user-service a vérifié que web-app n'a plus besoin de /user-service/v1/ et que web-app ne nécessite pas de rollback, elle peut déployer du code qui supprime l'ancien point de terminaison /user-service/v1/ et tout code temporaire nécessaire à sa compatibilité.

Bien que toute cette activité puisse sembler lourde, il s'agit d'un processus essentiel dans les applications basées sur des microservices. En outre, c'est précisément le processus qui rend possible des cycles de publication de développement indépendants. Précisons que ce processus semble être assez dépendant, mais il est important de noter que chaque étape mentionnée ci-dessus peut se dérouler selon des échéances indépendantes, et que la restauration par progression et le rollback se produisent dans le cadre d'un seul microservice. Seul l'ordre des étapes est fixe et celles-ci peuvent s'échelonner sur plusieurs heures, jours ou semaines.

Étapes suivantes

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Python 2