我们是怎么实现gRPC CodeFirst-生成proto
前言:
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
上一篇: pyton中的__str__函数
下一篇: ASP.NET实现省市联动