Condividi tramite


Controllo delle versioni dei servizi gRPC

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Di James Newton-King

Le nuove funzionalità aggiunte a un'app possono richiedere che i servizi gRPC forniti ai client cambino, a volte in modi imprevisti e di rilievo. Quando i servizi gRPC cambiano:

  • È consigliabile prendere in considerazione il modo in cui le modifiche influisce sui client.
  • Deve essere implementata una strategia di controllo delle versioni per supportare le modifiche.

Compatibilità con le versioni precedenti

Il protocollo gRPC è progettato per supportare i servizi che cambiano nel tempo. In genere, le aggiunte ai servizi e ai metodi gRPC non causano interruzioni. Le modifiche non di rilievo consentono ai client esistenti di continuare a funzionare senza modifiche. La modifica o l'eliminazione di servizi gRPC causano modifiche di rilievo. Quando i servizi gRPC hanno modifiche di rilievo, i client che usano tale servizio devono essere aggiornati e ridistribuiti.

L'esecuzione di modifiche non di rilievo a un servizio offre numerosi vantaggi:

  • I client esistenti continuano a essere eseguiti.
  • Evita di collaborare con la notifica ai client di modifiche di rilievo e l'aggiornamento.
  • È necessario documentare e gestire una sola versione del servizio.

Modifiche che non causano un'interruzione

Queste modifiche non causano interruzioni a livello di protocollo gRPC e a livello binario .NET.

  • Aggiunta di un nuovo servizio
  • Aggiunta di un nuovo metodo a un servizio
  • Aggiunta di un campo a un messaggio di richiesta: i campi aggiunti a un messaggio di richiesta vengono deserializzati con il valore predefinito nel server quando non è impostato. Per essere una modifica non di rilievo, il servizio deve avere esito positivo quando il nuovo campo non è impostato dai client meno recenti.
  • Aggiunta di un campo a un messaggio di risposta: se un client meno recente non è stato aggiornato con il nuovo campo, il valore viene deserializzato nella raccolta di campi sconosciuti del messaggio di risposta.
  • Aggiunta di un valore a un'enumerazione : le enumerazioni vengono serializzate come valore numerico. I nuovi valori di enumerazione vengono deserializzati nel client al valore di enumerazione senza un nome di enumerazione. Per essere una modifica non di rilievo, i client meno recenti devono essere eseguiti correttamente quando si riceve il nuovo valore di enumerazione.

Modifiche di rilievo binarie

Le modifiche seguenti non causano interruzioni a livello di protocollo gRPC, ma il client deve essere aggiornato se esegue l'aggiornamento all'assembly .NET del contratto o del client più recente .proto . La compatibilità binaria è importante se si prevede di pubblicare una libreria gRPC in NuGet.

  • Rimozione di un campo: i valori da un campo rimosso vengono deserializzati nei campi sconosciuti di un messaggio. Non si tratta di una modifica che causa un'interruzione del protocollo gRPC, ma il client deve essere aggiornato se esegue l'aggiornamento al contratto più recente. È importante che un numero di campo rimosso non venga riutilizzato accidentalmente in futuro. Per assicurarsi che ciò non accada, specificare i numeri di campo eliminati e i nomi nel messaggio usando la parola chiave riservata di Protobuf.
  • Ridenominazione di un messaggio : i nomi dei messaggi non vengono in genere inviati in rete, quindi non si tratta di una modifica che causa un'interruzione del protocollo gRPC. Il client dovrà essere aggiornato se esegue l'aggiornamento al contratto più recente. Una situazione in cui i nomi dei messaggi vengono inviati in rete è con i campi Any , quando il nome del messaggio viene usato per identificare il tipo di messaggio.
  • Annidamento o annullamento di un messaggio: i tipi di messaggio possono essere annidati. L'annidamento o l'annullamento di un messaggio modifica il nome del messaggio. La modifica della modalità di annidamento di un tipo di messaggio ha lo stesso impatto sulla compatibilità della ridenominazione.
  • Modifica csharp_namespace : la modifica csharp_namespace modifica modifica lo spazio dei nomi dei tipi .NET generati. Non si tratta di una modifica che causa un'interruzione del protocollo gRPC, ma il client deve essere aggiornato se esegue l'aggiornamento al contratto più recente.

Modifiche che causano un'interruzione del protocollo

Di seguito sono riportati i protocolli e le modifiche di rilievo binarie:

  • Ridenominazione di un campo : con il contenuto Protobuf, i nomi dei campi vengono usati solo nel codice generato. Il numero di campo viene usato per identificare i campi nella rete. La ridenominazione di un campo non è una modifica che causa un'interruzione del protocollo per Protobuf. Tuttavia, se un server usa contenuto JSON, la ridenominazione di un campo è una modifica che causa un'interruzione.
  • Modifica del tipo di dati di un campo: la modifica del tipo di dati di un campo in un tipo incompatibile genererà errori durante la deserializzazione del messaggio. Anche se il nuovo tipo di dati è compatibile, è probabile che il client debba essere aggiornato per supportare il nuovo tipo se esegue l'aggiornamento al contratto più recente.
  • Modifica di un numero di campo: con payload Protobuf, il numero di campo viene usato per identificare i campi nella rete.
  • Ridenominazione di un pacchetto, di un servizio o di un metodo : gRPC usa il nome del pacchetto, il nome del servizio e il nome del metodo per compilare l'URL. Il client ottiene uno stato UNIMPLEMENTED dal server.
  • Rimozione di un servizio o di un metodo : il client ottiene uno stato UNIMPLEMENTED dal server quando chiama il metodo rimosso.

Modifiche che causano un'interruzione del comportamento

Quando si apportano modifiche non di rilievo, è anche necessario valutare se i client meno recenti possono continuare a lavorare con il nuovo comportamento del servizio. Ad esempio, l'aggiunta di un nuovo campo a un messaggio di richiesta:

  • Non è una modifica che causa un'interruzione del protocollo.
  • Se il nuovo campo non è impostato, restituisce uno stato di errore nel server, viene apportata una modifica che causa un'interruzione per i client precedenti.

La compatibilità del comportamento è determinata dal codice specifico dell'app.

Servizi per il numero di versione

I servizi devono cercare di rimanere compatibili con le versioni precedenti dei client precedenti. Le modifiche apportate alla tua app potrebbero richiedere modifiche di rilievo. L'interruzione dei client precedenti e l'uso forzato dell'aggiornamento insieme al servizio non è un'esperienza utente ottimale. Un modo per mantenere la compatibilità con le versioni precedenti apportando modifiche di rilievo consiste nel pubblicare più versioni di un servizio.

gRPC supporta un identificatore di pacchetto facoltativo, che funziona in modo molto simile a uno spazio dei nomi .NET. In effetti, package verrà usato come spazio dei nomi .NET per i tipi .NET generati se option csharp_namespace non è impostato nel .proto file. Il pacchetto può essere usato per specificare un numero di versione per il servizio e i relativi messaggi:

syntax = "proto3";

package greet.v1;

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

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Il nome del pacchetto viene combinato con il nome del servizio per identificare un indirizzo del servizio. Un indirizzo del servizio consente l'hosting side-by-side di più versioni di un servizio:

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

Le implementazioni del servizio con versione vengono registrate in Startup.cs:

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

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

L'inclusione di un numero di versione nel nome del pacchetto consente di pubblicare una versione v2 del servizio con modifiche di rilievo, continuando a supportare i client meno recenti che chiamano la versione v1 . Dopo aver aggiornato i client per l'uso del servizio v2 , è possibile scegliere di rimuovere la versione precedente. Quando si prevede di pubblicare più versioni di un servizio:

  • Evitare di interrompere le modifiche, se ragionevole.
  • Non aggiornare il numero di versione a meno che non vengano apportate modifiche di rilievo.
  • Aggiornare il numero di versione quando si apportano modifiche di rilievo.

La pubblicazione di più versioni di un servizio la duplica. Per ridurre la duplicazione, è consigliabile spostare la logica di business dalle implementazioni del servizio a una posizione centralizzata che può essere riutilizzata dalle implementazioni precedenti e nuove:

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)
            });
        }
    }
}

I servizi e i messaggi generati con nomi di pacchetto diversi sono tipi .NET diversi. Lo spostamento della logica di business in una posizione centralizzata richiede il mapping dei messaggi ai tipi comuni.

Risorse aggiuntive