我們是怎么實現gRPC CodeFirst-生成proto


前言:

gRPC默認是ProtoFirst的,即先寫 proto文件,再生成代碼,需要人工維護proto,生成的代碼也不友好,所以出現了gRPC CodeFirst,下面來說說我們是怎么實現gRPC CodeFirst

 

目錄:

實現和WCF一樣的CodeFirst

(1). 實現gRPC CodeFirst,  簡化WCF一定要抽取接口的問題

(2). 通過代碼生成proto和注釋,給第三方語言使用

(3). 實現gRPC DashBoard,用於Http遠程調用和管理

(4). 實現gRPC scope注入的三種方式(asp.net core3.0 grpc默認是scope)

(5). 實現服務注冊與發現

(6). 實現分布式日志跟蹤

(7). 日志監控等等

 

 

我們是怎么實現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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM