Controle de versão de serviços gRPC
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.
Importante
Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.
Para a versão atual, consulte a versão .NET 9 deste artigo.
Novos recursos adicionados a um aplicativo podem exigir que os serviços gRPC fornecidos aos clientes sejam alterados, às vezes de maneiras inesperadas e interruptivas. Quando os serviços gRPC são alterados:
- Deve-se considerar como as alterações afetam os clientes.
- Uma estratégia de controle de versão para dar suporte a alterações deve ser implementada.
Compatibilidade com versões anteriores
O protocolo gRPC foi projetado para dar suporte a serviços que mudam ao longo do tempo. Em geral, as adições aos serviços e métodos gRPC não são interruptivas. As alterações não interruptivas permitem que os clientes existentes continuem funcionando sem alterações. Alterar ou excluir serviços gRPC são alterações interruptivas. Quando os serviços gRPC têm alterações interruptivas, os clientes que usam esse serviço precisam ser atualizados e reimplantados.
Fazer alterações não interruptivas em um serviço tem uma série de benefícios:
- Os clientes existentes continuam a ser executados.
- Evita o trabalho envolvido em notificar clientes sobre alterações interruptivas e atualizá-las.
- Apenas uma versão do serviço precisa ser documentada e mantida.
Alterações não relacionadas à falha
Essas alterações não são interruptivas em um nível de protocolo gRPC e no nível binário do .NET.
- Adicionando um novo serviço
- Adicionando um novo método a um serviço
- Adicionar um campo a uma mensagem de solicitação – os campos adicionados a uma mensagem de solicitação são desserializados com o valor padrão no servidor quando não definido. Para ser uma alteração não interruptiva, o serviço deve ter êxito quando o novo campo não é definido por clientes mais antigos.
- Adição de um campo a uma mensagem de resposta – se a atualização do novo campo não tiver sido feita para um cliente mais antigo, o valor será desserializado na coleção de campos desconhecidos da mensagem de resposta.
- Adicionar um valor a uma enumeração – Enumerações são serializadas como um valor numérico. Novos valores de enumeração são desserializados no cliente para o valor de enumeração sem um nome de enumeração. Para ser uma alteração não interruptiva, os clientes mais antigos devem ser executados corretamente ao receber o novo valor de enumeração.
Alterações interruptivas binárias
As alterações a seguir não são interruptivas em um nível de protocolo gRPC, mas o cliente precisa ser atualizado se atualizar para o contrato mais recente .proto
ou assembly .NET do cliente. A compatibilidade binária é importante se você planeja publicar uma biblioteca gRPC no NuGet.
- Remover um campo – os valores de um campo removido são desserializados para os campos desconhecidos de uma mensagem. Essa não é uma alteração interruptiva do protocolo gRPC, mas o cliente precisa ser atualizado se atualizar para o contrato mais recente. É importante que um número de campo removido não seja reutilizado acidentalmente no futuro. Para garantir que isso não aconteça, especifique os números e nomes de campo excluídos na mensagem usando a palavra-chave reservada do Protobuf.
- Renomear uma mensagem – os nomes de mensagem normalmente não são enviados na rede, portanto, essa não é uma alteração interruptiva do protocolo gRPC. O cliente precisará ser atualizado se atualizar para o contrato mais recente. Uma situação em que os nomes de mensagem são enviados na rede é com Qualquer campo, quando o nome da mensagem é usado para identificar o tipo de mensagem.
- Aninhar ou desaninhar de uma mensagem – os tipos de mensagem podem ser aninhados. Aninhar ou desaninhar uma mensagem altera o nome da mensagem. Alterar como um tipo de mensagem é aninhado tem o mesmo impacto na compatibilidade que a renomeação.
- Alterar csharp_namespace – Alterar
csharp_namespace
alterará o namespace de tipos .NET gerados. Essa não é uma alteração interruptiva do protocolo gRPC, mas o cliente precisa ser atualizado se atualizar para o contrato mais recente.
Alterações interruptivas no Protocolo
Os seguintes itens são alterações de falha de protocolo e binárias:
- Renomear um campo – com o conteúdo do Protobuf, os nomes de campo são usados apenas no código gerado. O número do campo é usado para identificar campos na rede. Renomear um campo não é uma alteração de falha de protocolo para Protobuf. No entanto, se um servidor estiver usando o conteúdo JSON, a renomeação de um campo será uma alteração interruptiva.
- Alterar um tipo de dados de campo – alterar o tipo de dados de um campo para um tipo incompatível causará erros ao desserializar a mensagem. Mesmo que o novo tipo de dados seja compatível, é provável que o cliente precise ser atualizado para dar suporte ao novo tipo se atualizar para o contrato mais recente.
- Alterar um número de campo – com cargas protobuf, o número do campo é usado para identificar campos na rede.
- Renomear um pacote, serviço ou método – o gRPC usa o nome do pacote, o nome do serviço e o nome do método para compilar a URL. O cliente obtém um status UNIMPLEMENTED do servidor.
- Remover um serviço ou método – o cliente obtém um status UNIMPLEMENTED do servidor ao chamar o método removido.
Alterações interruptivas de comportamento
Ao fazer alterações não interruptivas, você também deve considerar se os clientes mais antigos podem continuar trabalhando com o novo comportamento de serviço. Por exemplo, adicionar um novo campo a uma mensagem de solicitação:
- Não é uma alteração interruptiva de protocolo.
- Retornar um erro status no servidor se o novo campo não estiver definido o torna uma alteração interruptiva para clientes antigos.
A compatibilidade de comportamento é determinada pelo código específico do aplicativo.
Serviços de número de versão
Os serviços devem se esforçar para permanecer compatíveis com versões anteriores com clientes antigos. Eventualmente, as alterações em seu aplicativo podem exigir alterações interruptivas. Quebrar clientes antigos e forçá-los a serem atualizados junto com seu serviço não é uma boa experiência do usuário. Uma maneira de manter a compatibilidade com versões anteriores ao fazer alterações interruptivas é publicar várias versões de um serviço.
O gRPC dá suporte a um especificador de pacote opcional, que funciona muito parecido com um namespace do .NET. Na verdade, o package
será usado como o namespace do .NET para tipos .NET gerados se option csharp_namespace
não estiver definido no .proto
arquivo. O pacote pode ser usado para especificar um número de versão para seu serviço e suas mensagens:
syntax = "proto3";
package greet.v1;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
O nome do pacote é combinado com o nome do serviço para identificar um endereço de serviço. Um endereço de serviço permite que várias versões de um serviço sejam hospedadas lado a lado:
greet.v1.Greeter
greet.v2.Greeter
As implementações do serviço com versão são registradas em Startup.cs
:
app.UseEndpoints(endpoints =>
{
// Implements greet.v1.Greeter
endpoints.MapGrpcService<GreeterServiceV1>();
// Implements greet.v2.Greeter
endpoints.MapGrpcService<GreeterServiceV2>();
});
Incluir um número de versão no nome do pacote oferece a oportunidade de publicar uma versão v2 do serviço com alterações interruptivas, enquanto continua a dar suporte a clientes mais antigos que chamam a versão v1 . Depois que os clientes tiverem sido atualizados para usar o serviço v2 , você poderá optar por remover a versão antiga. Ao planejar a publicação de várias versões de um serviço:
- Evite alterações interruptivas se for razoável.
- Não atualize o número de versão, a menos que faça alterações interruptivas.
- Atualize o número de versão quando você fizer alterações interruptivas.
A publicação de várias versões de um serviço o duplica. Para reduzir a duplicação, considere mover a lógica de negócios das implementações de serviço para um local centralizado que possa ser reutilizado pelas implementações antigas e novas:
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)
});
}
}
}
Serviços e mensagens gerados com nomes de pacote diferentes são tipos diferentes do .NET. Mover a lógica de negócios para um local centralizado requer o mapeamento de mensagens para tipos comuns.