关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

怎么实现gRPC CodeFirst

发布时间:2020-04-02 00:00:00

前言:

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

    /// /// 生成Grpc方法(CodeFirst方式)/// /// /// /// /// /// /// /// /// public static MethodBuildMethod(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(typeof(TRequest).Name);
            ProtoGenerator.AddProto(typeof(TResponse).Name);
        }#endregionvar request = Marshallers.Create((arg) => ProtobufExtensions.Serialize(arg), data => ProtobufExtensions.Deserialize(data));var response = Marshallers.Create((arg) => ProtobufExtensions.Serialize(arg), data => ProtobufExtensions.Deserialize(data));return new Method(mType, serviceName, methodName, request, response);
    }

 

2.不重复造轮子,通过protobuf-net的Serializer.GetProto()来生成请求参数和返回参数的proto

(1).这里简单过滤了重复的proto,但GetProto()会把依赖的类都生成proto,这样公用类就会生成多份,需要再次过滤重复即可

(2).生成message非关键代码这里我就不列出来了,都是字符串拼接的活

    /// /// 添加proto/// public static void AddProto(string entityName)
    {if (!ProtoMethodInfo.Protos.ContainsKey(entityName))
        {var msg = Serializer.GetProto(ProtoBuf.Meta.ProtoSyntax.Proto3);
            ProtoMethodInfo.Protos.TryAdd(entityName, msg.FilterHead().AddMessageComment());
        }
    }

 

 3.服务方法的proto就更简单了,直接根据方法类型拼出来即可

    /// /// 生成grpc的service的proto内容/// private static string GenGrpcServiceProto(string msgProtoName, string pkgName, string srvName, ListmethodInfo, 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

 


/template/Home/Zkeys/PC/Static