Contrats, adressage et API pour les microservices

ID de la région

Le REGION_ID est un code abrégé que Google attribue en fonction de la région que vous sélectionnez lors de la création de votre application. Le code ne correspond pas à un pays ou une province, même si certains ID de région peuvent ressembler aux codes de pays et de province couramment utilisés. Pour les applications créées après février 2020, REGION_ID.r est inclus dans les URL App Engine. Pour les applications existantes créées avant cette date, l'ID de région est facultatif dans l'URL.

En savoir plus sur les ID de région

En général, les microservices sur App Engine s'appellent les uns les autres à l'aide d'API RESTful basées sur HTTP. Vous pouvez également appeler des microservices en arrière-plan à l'aide des files d'attente de tâches, auquel cas 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, utilisez l'URL suivante :
https://PROJECT_ID.REGION_ID.r.appspot.com

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

https://user-service-dot-my-app.REGION_ID.r.appspot.com

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

https://banana-dot-user-service-dot-my-app.REGION_ID.r.appspot.com

Notez que si vous déployez une deuxième version de code autre que celle par défaut, nommée cherry sur le service default, vous pouvez y accéder en utilisant l'URL suivante :

https://cherry-dot-my-app.REGION_ID.r.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 :


https://PROJECT_ID.REGION_ID.r.appspot.com

https://SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.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.

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 principale

  • 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, votre 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. Autrement dit, aucun déploiement de code ne peut les mettre 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 pourraient utiliser les URL suivantes :

http://user-service-v1.my-app.REGION_ID.r.appspot.com/user-service/v1/
http://user-service-v2.my-app.REGION_IDappspot.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/ et /v2/ du chemin d'accès sont redondantes dans ce modèle et pourraient être supprimées, bien qu'elles puissent toujours être utiles dans 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 de façon silencieuse 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 suivante nécessiterait une nouvelle version 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 côte à côte la nouvelle version de code nommée banana. Notez que les URL de microservice de ces deux versions sont identiques (/user-service/v1/) puisque nous déployons une modification non destructive de l'API mineure.

App Engine comporte des mécanismes permettant de migrer automatiquement le trafic de apple vers banana en marquant la nouvelle version de code banana comme 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. 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, c'est-à-dire en rétablissant l'ancienne version de diffusion par défaut, soit apple dans notre exemple. Toutes les nouvelles requêtes seront réacheminées vers l'ancienne version du code et aucune nouvelle requête ne sera acheminé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'image suivante montre les paramètres de répartition du trafic dans la console :

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

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'effectuez généralement pas de répartition du trafic ni de tests A/B car la version de l'API de dernière génération est une nouvelle URL, comme par exemple /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 en cours de diffusion 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 d'un microservice web-app, qui dépend d'un autre microservice nommé user-service. Supposons que le microservice user-service nécessite la modification d'une mise en œuvre sous-jacente qui rendra incompatible l'ancienne version d'API majeure actuellement utilisée par web-app, par exemple pour réduire firstName et lastName à un seul champ nommé name. Autrement dit, 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 préservant la compatibilité 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, après 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 le 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