Partager via


Contrôle de version des services gRPC

Par James Newton-King

Les nouvelles fonctionnalités ajoutées à une application exigent éventuellement une modification des services gRPC fournis aux clients, parfois de manière inattendue et importante. Lorsque les services gRPC changent :

  • Il convient de réfléchir à l’impact des modifications sur les clients.
  • Une stratégie de contrôle de version pour prendre en charge les modifications doit être implémentée.

Compatibilité descendante

Le protocole gRPC est conçu pour prendre en charge les services qui changent au fil du temps. En règle générale, les ajouts apportés aux méthodes et aux services gRPC ne sont pas importants. Les modifications non importantes permettent aux clients existants de continuer à fonctionner sans modifications. La modification ou la suppression des services gRPC sont des modifications importantes. Lorsque les services gRPC ont des modifications importantes, les clients qui utilisent ce service doivent être mis à jour et redéployés.

Apporter des modifications non importantes à un service présente plusieurs avantages :

  • Les clients existants continuent à s’exécuter.
  • dispense de travail lié à la notification aux clients des modifications importantes et à leur mise à jour.
  • Une seule version du service doit être documentée et conservée.

Changements non cassants

Ces modifications ne sont pas importantes au niveau du protocole gRPC et au niveau binaire .NET.

  • Ajout d’un nouveau service
  • Ajout d’une nouvelle méthode à un service
  • Ajout d’un champ à un message de requête : les champs ajoutés à un message de requête sont désérialisés avec la valeur par défaut sur le serveur lorsqu’ils ne sont pas définis. Pour être une modification non importante, le service doit aboutir lorsque le nouveau champ n’est pas défini par des clients plus anciens.
  • Ajout d’un champ à un message de réponse : les champs ajoutés à un message de réponse sont désérialisés dans la collection de champs inconnus du message sur le client.
  • Ajout d’une valeur à une énumération : les énumérations sont sérialisées en tant que valeur numérique. Les nouvelles valeurs d’énumération sont désérialisées sur le client à la valeur d’énumération sans nom d’énumération. pour être une modification non importante, les clients plus anciens doivent s’exécuter correctement lors de la réception de la nouvelle valeur d’énumération.

Modifications binaires non importantes

Les modifications suivantes ne sont pas importantes au niveau du protocole gRPC, mais le client doit être mis à jour s’il effectue une mise à niveau vers le dernier contrat .proto ou l’assembly .NET du client. La compatibilité binaire est importante si vous envisagez de publier une bibliothèque gRPC sur NuGet.

  • Suppression d’un champ : les valeurs d’un champ supprimé sont désérialisées dans les champs inconnus d’un message. Il ne s’agit pas d’une modification importante du protocole gRPC, mais le client doit être mis à jour s’il effectue une mise à niveau vers le dernier contrat. Il est important qu’un numéro de champ supprimé ne soit pas réutilisé accidentellement à l’avenir. Pour vous assurer que cela ne se produit pas, spécifiez les numéros et noms de champs supprimés sur le message à l’aide du mot clé réservé de Protobuf.
  • Renommage d’un message : les noms de message ne sont généralement pas envoyés sur le réseau. Il ne s’agit donc pas d’une modification importante du protocole gRPC. Le client doit être mis à jour s’il effectue une mise à niveau vers le contrat le plus récent. L’une des situations où les noms de message sont envoyés sur le réseau est avec tous les champs, lorsque le nom du message est utilisé pour identifier le type de message.
  • Imbrication ou désimbrication d’un message : les types de messages peuvent être imbriqués. L’imbrication ou la désimbrication d’un message modifie le nom de message. La modification de la façon dont un type de message est imbriqué a le même impact sur la compatibilité que le renommage.
  • Modification de csharp_namespace : la modification csharp_namespace modifie l’espace de noms des types .NET générés. Il ne s’agit pas d’une modification importante du protocole gRPC, mais le client doit être mis à jour s’il effectue une mise à niveau vers le dernier contrat.

Modifications importantes du protocole

Les éléments suivants sont des modifications importantes binaires et du protocole :

  • Renommage d’un champ : avec le contenu Protobuf, les noms de champs sont utilisés uniquement dans le code généré. Le numéro de champ est utilisé pour identifier les champs sur le réseau. Le renommage d’un champ n’est pas une modification importante du protocole pour Protobuf. Toutefois, si un serveur utilise du contenu JSON, le renommage d’un champ est une modification importante.
  • Modification d’un type de données de champ : la modification du type de données d’un champ en un type incompatible entraîne des erreurs lors de la désérialisation du message. Même si le nouveau type de données est compatible, il est probable que le client doit être mis à jour pour prendre en charge le nouveau type s’il effectue une mise à niveau vers le dernier contrat.
  • Modification d’un numéro de champ : avec les charges utiles de Protobuf, le numéro de champ est utilisé pour identifier les champs sur le réseau.
  • Renommage d’un package, d’un service ou d’une méthode : gRPC utilise le nom du package, le nom du service et le nom de la méthode pour générer l’URL. Le client obtient du serveur un état NON IMPLÉMENTÉ.
  • Suppression d’un service ou d’une méthode : le client obtient du serveur un état NON IMPLÉMENTÉ lors de l’appel de la méthode supprimée.

Modifications importantes du comportement

Lorsque vous apportez des modifications non importantes, vous devez également déterminer si les clients plus anciens peuvent continuer à fonctionner avec le nouveau comportement du service. Par exemple, l’ajout d’un nouveau champ à un message de requête :

  • N’est pas une modification importante du protocole.
  • Le renvoi d’un état d’erreur au serveur si le nouveau champ n’est pas défini en fait une modification importante pour les anciens clients.

La compatibilité du comportement est déterminée par votre code spécifique à l’application.

Services de numéro de version

Les services doivent s’efforcer de rester rétrocompatibles avec les anciens clients. Les modifications apportées à votre application peuvent éventuellement exiger des modifications importantes. Arrêter les anciens clients et les mettre à jour de force avec votre service n’est pas une bonne expérience utilisateur. Un moyen de maintenir la compatibilité descendante tout en apportant des modifications importantes consiste à publier plusieurs versions d’un service.

gRPC prend en charge un spécificateur de package optionnel, qui fonctionne comme un espace de noms .NET. En fait, le package sera utilisé comme espace de noms .NET pour les types .NET générés si option csharp_namespace n’est pas défini dans le fichier .proto. Le package peut être utilisé pour spécifier un numéro de version pour votre service et ses messages :

syntax = "proto3";

package greet.v1;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Le nom du package est combiné avec le nom du service pour identifier une adresse de service. Une adresse de service permet d’héberger côte à côte plusieurs versions d’un service :

  • greet.v1.Greeter
  • greet.v2.Greeter

Les implémentations du service versionné sont inscrites dans Startup.cs :

app.UseEndpoints(endpoints =>
{
    // Implements greet.v1.Greeter
    endpoints.MapGrpcService<GreeterServiceV1>();

    // Implements greet.v2.Greeter
    endpoints.MapGrpcService<GreeterServiceV2>();
});

L’inclusion d’un numéro de version dans le nom du package vous permet de publier une version v2 de votre service avec des modifications importantes, tout en continuant à prendre en charge les clients plus anciens qui appellent la version v1. Une fois que les clients ont été mis à jour pour utiliser le service v2, vous pouvez supprimer l’ancienne version. Lors de la planification de la publication de plusieurs versions d’un service :

  • Évitez les modifications importantes si cela est raisonnable.
  • Ne mettez pas à jour le numéro de version, sauf si vous apportez des modifications importantes.
  • Mettez à jour le numéro de version lorsque vous apportez des modifications importantes.

La publication de plusieurs versions d’un service le double. Pour réduire la duplication, pensez à déplacer la logique métier des implémentations de service vers un emplacement centralisé qui peut être réutilisé par les anciennes et les nouvelles implémentations :

using Greet.V1;
using Grpc.Core;
using System.Threading.Tasks;

namespace Services
{
    public class GreeterServiceV1 : Greeter.GreeterBase
    {
        private readonly IGreeter _greeter;
        public GreeterServiceV1(IGreeter greeter)
        {
            _greeter = greeter;
        }

        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply
            {
                Message = _greeter.GetHelloMessage(request.Name)
            });
        }
    }
}

Les services et les messages générés avec différents noms de package sont des types .NET différents. Le déplacement de la logique métier vers un emplacement centralisé exige le mappage des messages à des types courants.

Ressources supplémentaires