我们是怎么实现gRPC CodeFirst-生成proto
阅读原文时间:2023年07月14日阅读:1

前言:

gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst

目录:

实现和WCF一样的CodeFirst

(1). 实现gRPC** CodeFirst,  简化WCF一定要抽取接口的问题**

(2). 通过代码生成proto和注释,给第三方语言使用

(3). 实现gRPC DashBoard,用于Http远程调用和管理

(4). 实现gRPC scope注入的三种方式(asp.net core3.0 grpc默认是scope)

(5). 实现服务注册与发现

(6). 实现分布式日志跟踪

(7). 日志监控等等

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

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章