添加到应用中的新功能可能要求提供给客户端的 gRPC 服务更改,有时会出现意外和中断的方式。 当 gRPC services 更改时:
GRPC 协议旨在支持随时间变化的服务。 通常,对 gRPC 服务和方法的添加是不间断的。 非重大更改允许现有的客户端无需更改即可继续工作。 更改或删除 gRPC 服务是重大更改。 当 gRPC 服务有重大更改时,必须更新和重新部署使用该服务的客户端。
对服务进行非重大更改有很多好处:
在 gRPC 协议级别和 .NET 二进制级别,这些更改不会中断。
以下更改在 gRPC 协议级别不会中断,但当客户端升级到最新的 proto协定或客户端 .net 程序集时,需要对其进行更新。 如果计划将 gRPC 库发布到 NuGet,则二进制兼容性非常重要。
csharp_namespace
将更改所生成的 .net 类型的命名空间。 这不是一种 gRPC 的协议重大更改,但如果要升级到最新的合同,则需要更新客户端。以下各项是协议和二进制的重大更改:
在进行非重大更改时,您还必须考虑到较旧的客户端是否可以继续使用新的服务行为。 例如,将新字段添加到请求消息:
行为兼容性由应用程序特定的代码确定。
服务应尽量保持与旧客户端的向后兼容。 最终,对应用所做的更改可能需要进行重大更改。 打破旧客户端,并强制将它们与服务一起更新并不是一种很好的用户体验。 在进行重大更改时保持向后兼容性的一种方法是发布服务的多个版本。
gRPC 支持可选的包说明符,其功能与 .net 命名空间非常类似。 事实上,如果未在proto文件中设置 option csharp_namespace
,则 package
将用作生成的 .net 类型的 .net 命名空间。 包可用于指定服务及其消息的版本号:
syntax = "proto3"; package greet.v1; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
包名称与服务名称组合在一起,以标识服务地址。 服务地址允许并行承载服务的多个版本:
greet.v1.Greeter
greet.v2.Greeter
版本控制服务的实现在Startup.cs中注册:
app.UseEndpoints(endpoints => { // Implements greet.v1.Greeter endpoints.MapGrpcService<GreeterServiceV1>(); // Implements greet.v2.Greeter endpoints.MapGrpcService<GreeterServiceV2>(); });
如果在包名称中包含版本号,则可以通过重大更改发布服务的v2版本,同时继续支持调用v1版本的旧客户端。 当客户端更新为使用v2服务后,您可以选择删除旧版本。 计划发布服务的多个版本时:
发布服务的多个版本会将其重复。 若要减少重复,请考虑将业务逻辑从服务实现移动到可供旧实现和新实现重复使用的集中位置:
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) }); } } }
用不同的包名称生成的服务和消息是不同的 .net 类型。 将业务逻辑移动到集中位置需要将消息映射到常见类型。