当前位置: 移动技术网 > IT编程>开发语言>.net > 我们是怎么实现gRPC CodeFirst-生成proto

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

2020年04月01日  | 移动技术网IT编程  | 我要评论

钥钥聊天室,77bbbb,奇人奇事

前言:

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

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网