欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

我们是怎么实现gRPC CodeFirst-生成proto

程序员文章站 2022-04-27 18:38:16
前言: gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst 目录: 实现和WCF一样的CodeFirst (1). 实现gRPC CodeF ......

前言:

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

 

目录:

实现和wcf一样的codefirst

(1). 实现grpc codefirst,  简化wcf一定要抽取接口的问题

(2). 

(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