前言: 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一定要抽取介面的問題
(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