前言:
gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst
目录:
实现和WCF一样的CodeFirst
(1). 实现gRPC CodeFirst, 简化WCF一定要抽取接口的问题
(2). 通过代码生成proto和注释,给第三方语言使用
(3). 实现gRPC DashBoard,用于Http远程调用和管理
(4). 实现服务注册与发现
(5). 实现分布式日志跟踪
(6). 日志监控等等
我们是怎么实现gRPC CodeFirst
1.要实现CodeFirst先要了解ProtoFirst生成的代码,下面我截了部分生成代码
(1).关键是这个BindService,用于把实现的gRPC方法绑定到ServerServiceDefinition
public static partial class Greeter { static readonly string __ServiceName = "helloworld.Greeter"; static readonly grpc::Marshaller<global::Helloworld.HelloRequest> __Marshaller_helloworld_HelloRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloRequest.Parser.ParseFrom); static readonly grpc::Marshaller<global::Helloworld.HelloReply> __Marshaller_helloworld_HelloReply = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloReply.Parser.ParseFrom); static readonly grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply> __Method_SayHello = new grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>( grpc::MethodType.Unary, __ServiceName, "SayHello", __Marshaller_helloworld_HelloRequest, __Marshaller_helloworld_HelloReply); static readonly grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply> __Method_SayHelloStream = new grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>( grpc::MethodType.ClientStreaming, __ServiceName, "SayHelloStream", __Marshaller_helloworld_HelloRequest, __Marshaller_helloworld_HelloReply); /// <summary>Creates service definition that can be registered with a server</summary> /// <param name="serviceImpl">An object implementing the server-side handling logic.</param> public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl) { return grpc::ServerServiceDefinition.CreateBuilder() .AddMethod(__Method_SayHello, serviceImpl.SayHello) .AddMethod(__Method_SayHelloStream, serviceImpl.SayHelloStream).Build(); } /// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic. /// Note: this method is part of an experimental API that can change or be removed without any prior notice.</summary> /// <param name="serviceBinder">Service methods will be bound by calling <c>AddMethod</c> on this object.</param> /// <param name="serviceImpl">An object implementing the server-side handling logic.</param> public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl) { serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(serviceImpl.SayHello)); serviceBinder.AddMethod(__Method_SayHelloStream, serviceImpl == null ? null : new grpc::ClientStreamingServerMethod<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(serviceImpl.SayHelloStream)); } }
(2).__Marshaller_helloworld_HelloRequest这个是实现protobuffer的序列化和反序列化的一个委托
服务的请求参数和返回参数,我们使用Protobuf-net来实现序列化和反序列化,和 WCF一样需要给类打上标签
/// <summary> /// 加法请求参数 /// </summary> [ProtoContract] public class AddRequest { /// <summary> /// 第一个数字 /// </summary> [ProtoMember(1)] public int Num1 { get; set; } /// <summary> /// 第二个数字 /// </summary> [ProtoMember(2)] public int Num2 { get; set; } }
2.要实现CodeFirst需要实现这个BindService,把我们的Grpc方法添加到ServerServiceDefinition
(1).我们让Grpc服务实现IGrpcService空接口,用于标识是GrpcService
/// <summary> /// MathGrpc /// </summary> public class MathGrpc : IGrpcService { /// <summary> /// 加法 /// </summary> /// <param name="request"></param> /// <param name="context"></param> /// <returns></returns> public Task<IntMessage> Add(AddRequest request, ServerCallContext context) { var result = new IntMessage(); result.Value = request.Num1 + request.Num2; return Task.FromResult(result); } }
(2).获取实现了IGrpcService接口的类,然后反射获取方法,再添加到ServerServiceDefinition即可
这里调用了GrpcMethodHelper.AutoRegisterMethod()方法,这是通过反射自动注册GrpcMethod的方法
/// <summary> /// 注入IGrpcService /// </summary> /// <param name="grpcServices"></param> /// <returns></returns> private ServerBuilder UseGrpcService(IEnumerable<IGrpcService> grpcServices) { var builder = ServerServiceDefinition.CreateBuilder(); grpcServices.ToList().ForEach(grpc => GrpcMethodHelper.AutoRegisterMethod(grpc, builder)); _serviceDefinitions.Add(builder.Build()); return this; }
未完,待续,欢迎评论拍砖
这些功能早在2018年就已经实现并运行在生产,感兴趣的同学可以去 github上查看,你要的都有,欢迎提issue
出处:https://www.cnblogs.com/rabbityi/p/12593922.html
=========================================================================================
前言:
gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst
目录:
实现和WCF一样的CodeFirst
(1). 实现gRPC CodeFirst, 简化WCF一定要抽取接口的问题
(2). 通过代码生成proto和注释,给第三方语言使用
(3). 实现gRPC DashBoard,用于Http远程调用和管理
(4). 实现服务注册与发现
(5). 实现分布式日志跟踪
(6). 日志监控等等
我们是怎么实现gRPC CodeFirst-生成proto
1.怎么根据代码生成Proto,上文我们调用了GrpcMethodHelper.AutoRegisterMethod()方法,这是通过反射自动注册GrpcMethod的方法
(1).这里面调用了一个BuildMethod方法,用于生成grpc的序列化和反序列化的委托
(2).同时可以收集grpc方法和参数的信息,用于生成proto
/// <summary> /// 生成Grpc方法(CodeFirst方式) /// </summary> /// <typeparam name="TRequest"></typeparam> /// <typeparam name="TResponse"></typeparam> /// <param name="srv"></param> /// <param name="methodName"></param> /// <param name="package"></param> /// <param name="srvName"></param> /// <param name="mType"></param> /// <returns></returns> public static Method<TRequest, TResponse> BuildMethod<TRequest, TResponse>(this IGrpcService srv, string methodName, string package = null, string srvName = null, MethodType mType = MethodType.Unary) { var serviceName = srvName ?? GrpcExtensionsOptions.Instance.GlobalService ?? srv.GetType().Name; var pkg = package ?? GrpcExtensionsOptions.Instance.GlobalPackage; if (!string.IsNullOrWhiteSpace(pkg)) { serviceName = $"{pkg}.{serviceName}"; } #region 为生成proto收集信息 if (!(srv is IGrpcBaseService) || GrpcExtensionsOptions.Instance.GenBaseServiceProtoEnable) { ProtoInfo.Methods.Add(new ProtoMethodInfo { ServiceName = serviceName, MethodName = methodName, RequestName = typeof(TRequest).Name, ResponseName = typeof(TResponse).Name, MethodType = mType }); ProtoGenerator.AddProto<TRequest>(typeof(TRequest).Name); ProtoGenerator.AddProto<TResponse>(typeof(TResponse).Name); } #endregion var request = Marshallers.Create<TRequest>((arg) => ProtobufExtensions.Serialize<TRequest>(arg), data => ProtobufExtensions.Deserialize<TRequest>(data)); var response = Marshallers.Create<TResponse>((arg) => ProtobufExtensions.Serialize<TResponse>(arg), data => ProtobufExtensions.Deserialize<TResponse>(data)); return new Method<TRequest, TResponse>(mType, serviceName, methodName, request, response); }
2.不重复造轮子,通过protobuf-net的Serializer.GetProto()来生成请求参数和返回参数的proto
(1).这里简单过滤了重复的proto,但GetProto()会把依赖的类都生成proto,这样公用类就会生成多份,需要再次过滤重复即可
(2).生成message非关键代码这里我就不列出来了,都是字符串拼接的活
/// <summary> /// 添加proto /// </summary> public static void AddProto<TEntity>(string entityName) { if (!ProtoMethodInfo.Protos.ContainsKey(entityName)) { var msg = Serializer.GetProto<TEntity>(ProtoBuf.Meta.ProtoSyntax.Proto3); ProtoMethodInfo.Protos.TryAdd(entityName, msg.FilterHead().AddMessageComment<TEntity>()); } }
3.服务方法的proto就更简单了,直接根据方法类型拼出来即可
/// <summary> /// 生成grpc的service的proto内容 /// </summary> private static string GenGrpcServiceProto(string msgProtoName, string pkgName, string srvName, List<ProtoMethodInfo> methodInfo, bool spiltProto) { var sb = new StringBuilder(); sb.AppendLine("syntax = "proto3";"); if (!string.IsNullOrWhiteSpace(GrpcExtensionsOptions.Instance.ProtoNameSpace)) { sb.AppendLine("option csharp_namespace = "" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() + "";"); } if (!string.IsNullOrWhiteSpace(pkgName)) { sb.AppendLine($"package {pkgName.Trim()};"); } if (spiltProto) { sb.AppendLine(string.Format("import "{0}";", msgProtoName)); } sb.AppendLine(Environment.NewLine); sb.AppendLine("service " + srvName + " {"); var template = @" rpc {0}({1}) returns({2})"; methodInfo.ForEach(q => { var requestName = q.RequestName; var responseName = q.ResponseName; switch (q.MethodType) { case Core.MethodType.Unary: break; case Core.MethodType.ClientStreaming: requestName = "stream " + requestName; break; case Core.MethodType.ServerStreaming: responseName = "stream " + responseName; break; case Core.MethodType.DuplexStreaming: requestName = "stream " + requestName; responseName = "stream " + responseName; break; } ProtoCommentGenerator.AddServiceComment(q,sb); sb.AppendLine(string.Format(template, q.MethodName, requestName, responseName) + ";" + Environment.NewLine); }); sb.AppendLine("}"); return sb.ToString(); }
4.生成 proto没有注释,第三方对接时就尴尬了,虽然命名规范,但注释还是要有的,减少沟通成本
(1).我们通过在类和方法上加入注释,然后项目里设置生成xml注释文档
(2).生成proto时通过扫描xml注释文档来给proto加入注释即可
未完,待续,欢迎评论拍砖
这些功能早在2018年就已经实现并运行在生产,感兴趣的同学可以去 github(grpc.extensions) 上查看,你要的都有,欢迎提issue