前言:
gRPC 默认是 ProtoFirst 的, 即先写 proto 文件, 再生成代码, 需要人工维护 proto, 生成的代码也不友好, 所以出现了 gRPC CodeFirst, 下面来说说我们是怎么实现 gRPC CodeFirst
目录:
实现和 WCF 一样的 CodeFirst
(1). 实现 gRPCCodeFirst, 简化 WCF 一定要抽取接口的问题
(2). 通过代码生成 proto 和注释, 给第三方语言使用
(3). 实现 gRPC DashBoard, 用于 Http 远程调用和管理
(4). 实现服务注册与发现
(5). 实现分布式日志跟踪
(6). 日志监控等等
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
来源: https://www.cnblogs.com/rabbityi/p/12605202.html