Dapr 運用之集成 Asp.Net Core Grpc 調用篇

前置條件: 《Dapr 運用》html


改造 ProductService 以提供 gRPC 服務

  1. 從 NuGet 或程序包管理控制檯安裝 gRPC 服務必須的包java

    • Grpc.AspNetCore
  2. 配置 Http/2
    • gRPC 服務須要 Http/2 協議git

      public static IHostBuilder CreateHostBuilder(string[] args)
      {
          return Host.CreateDefaultBuilder(args)
              .ConfigureWebHostDefaults(webBuilder =>
              {
                  webBuilder.ConfigureKestrel(options =>
                  {
                      options.Listen(IPAddress.Loopback, 50![](https://img2018.cnblogs.com/blog/757544/201912/757544-20191218205830077-211023565.png)
      01, listenOptions =>
                      {
                          listenOptions.Protocols = HttpProtocols.Http2;
                      });
                  });
                  webBuilder.UseStartup<Startup>();
              });
      }
  3. 新建了 product.proto 以定義 GRPC 服務,它須要完成的內容是返回全部產品集合,固然目前產品內容只有一個 IDgithub

    • 定義產品 protoweb

      syntax = "proto3";
      
      package productlist.v1;
      
      option csharp_namespace = "ProductList.V1";
      
      service ProductRPCService{
          rpc GetAllProducts(ProductListRequest) returns(ProductList);
      }
      
      message ProductListRequest{
      
      }
      
      message ProductList {
          repeated Product results = 1;
      }
      
      message Product {
          string ID=1;
      }
      說明
      • 定義產品列表 gRPC 服務,得益於宇宙第一 IDE Visual Studio ,只要添加 Grpc.Tools 包就能夠自動生成 gRPC 所需的代碼,這裏再也不須要手動去添加 Grpc.Tools ,官方提供的 Grpc.AspNetCore 中已經集成了
      • 定義了一個服務 ProductRPCService
      • 定義了一個函數 ProductRPCService
      • 定義了一個請求構造 ProductListRequest ,內容爲空
      • 定義了一個請求返回構造 ProductList ,使用 repeated 代表返回數據是集合
      • 定義了一個數據集合中的一個對象 Product
    • 添加 ProductListService 文件,內容以下app

      public class ProductListService : ProductRPCService.ProductRPCServiceBase
          {
              private readonly ProductContext _productContext;
      
              public ProductListService(ProductContext productContext)
              {
                  _productContext = productContext;
              }
      
              public override async Task<ProductList.V1.ProductList> GetAllProducts(ProductListRequest request, ServerCallContext context)
              {
                  IList<Product> results = await _productContext.Products.ToListAsync();
                  var productList = new ProductList.V1.ProductList();
                  foreach (Product item in results)
                  {
                      productList.Results.Add(new ProductList.V1.Product
                      {
                          ID = item.ProductID.ToString()
                      });
                  }
      
                  return productList;
              }
          }
  4. 在 Startup.cs 修改代碼以下框架

    public void ConfigureServices(IServiceCollection services)
    {
        //啓用 gRPC 服務
        services.AddGrpc();
        services.AddTransient<ProductListService>();
        ...
    }

    這裏的 services.AddTransient (); 的緣由是在 Dapr 中須要使用構造器注入,以完成 GetAllProducts(...) 函數的調用 dom

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseRouting();
    
        app.UseEndpoints(endpoints =>
        {
            ...
    
            //添加 gRPC 到路由管道中
            endpoints.MapGrpcService<DaprClientService>();
        });
    }

    這裏添加的代碼的含義分別是啓用 gRPC 服務和添加 gRPC 路由。得益於 ASP.NET Core 中間件的優秀設計,ASP.NET Core 可同時支持 Http 服務。async

  5. 添加 daprclient.proto 文件以生成 Dapr Grpc 服務,daprclient.proto 內容以下ide

    syntax = "proto3";
    
    package daprclient;
    
    import "google/protobuf/any.proto";
    import "google/protobuf/empty.proto";
    import "google/protobuf/duration.proto";
    
    option java_outer_classname = "DaprClientProtos";
    option java_package = "io.dapr";
    
    // User Code definitions
    service DaprClient {
    rpc OnInvoke (InvokeEnvelope) returns (google.protobuf.Any) {}
    rpc GetTopicSubscriptions(google.protobuf.Empty) returns (GetTopicSubscriptionsEnvelope) {}
    rpc GetBindingsSubscriptions(google.protobuf.Empty) returns (GetBindingsSubscriptionsEnvelope) {}
    rpc OnBindingEvent(BindingEventEnvelope) returns (BindingResponseEnvelope) {}
    rpc OnTopicEvent(CloudEventEnvelope) returns (google.protobuf.Empty) {}
    }
    
    message CloudEventEnvelope {
    string id = 1;
    string source = 2;
    string type = 3;
    string specVersion = 4;
    string dataContentType = 5;
    string topic = 6;
    google.protobuf.Any data = 7;
    }
    
    message BindingEventEnvelope {
        string name = 1;
        google.protobuf.Any data = 2;
        map<string,string> metadata = 3;
    }
    
    message BindingResponseEnvelope {
    google.protobuf.Any data = 1;
    repeated string to = 2;
    repeated State state = 3;
    string concurrency = 4;
    }
    
    message InvokeEnvelope {
        string method = 1;
        google.protobuf.Any data = 2;
        map<string,string> metadata = 3;
    }
    
    message GetTopicSubscriptionsEnvelope {
    repeated string topics = 1;
    }
    
    message GetBindingsSubscriptionsEnvelope {
    repeated string bindings = 1;
    }
    
    message State {
    string key = 1;
    google.protobuf.Any value = 2;
    string etag = 3;
    map<string,string> metadata = 4;
    StateOptions options = 5;
    }
    
    message StateOptions {
    string concurrency = 1;
    string consistency = 2;
    RetryPolicy retryPolicy = 3;
    }
    
    message RetryPolicy {
    int32 threshold = 1;
    string pattern = 2;
    google.protobuf.Duration interval = 3;
    }
    說明
    • 此文件爲官方提供,Dapr 0.3 版本以前提供的已經生成好的代碼,如今看源碼能夠看出已經改成提供 proto 文件了,這裏我認爲提供 proto 文件比較合理
    • 此文件定義了5個函數,此文主要講的就是 OnInvoke() 函數
    • OnInvoke() 請求構造爲 InvokeEnvelope
      • method 提供調用方法名稱
      • data 請求數據
      • metadata 額外數據,此處使用鍵值對形式體現
  6. 建立 DaprClientService.cs 文件,此文件用於終結點路由,內容爲

    public class DaprClientService : DaprClient.DaprClientBase
    {
        private readonly ProductListService _productListService;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="ProductService" /> class.
        /// </summary>
        /// <param name="productListService"></param>
        public DaprClientService(ProductListService productListService)
        {
            _productListService = productListService;
        }
    
        public override async Task<Any> OnInvoke(InvokeEnvelope request, ServerCallContext context)
        {
            switch (request.Method)
            {
                case "GetAllProducts":
                    ProductListRequest productListRequest = ProductListRequest.Parser.ParseFrom(request.Data.Value);
                    ProductList.V1.ProductList productsList = await _productListService.GetAllProducts(productListRequest, context);
                    return Any.Pack(productsList);
            }
    
            return null;
        }
    }
    說明
    • 使用構造器注入已定義好的 ProductListService
    • InvokeEnvelope 中的 Method 用於路由數據
    • 使用 ProductListRequest.Parser.ParseFrom 轉換請求構造
    • 使用 Any.Pack() 打包須要返回的數據
  7. 運行 productService

    dapr run --app-id productService --app-port 5001 --protocol grpc dotnet run

小結
至此,ProductService 服務完成。此時 ProductService.Api.csproj Protobuf 內容爲

<ItemGroup>
  <Protobuf Include="Protos\daprclient.proto" GrpcServices="Server" />
 <Protobuf Include="Protos\productList.proto" GrpcServices="Server" />
 </ItemGroup>

改造 StorageService 服務以完成 Dapr GRPC 服務調用

  1. 添加 productList.proto 文件,內容同 ProductService 中的 productList.proto

  2. 添加 dapr.proto 文件,此文件也爲官方提供,內容爲

    syntax = "proto3";
    
    package dapr;
    
    import "google/protobuf/any.proto";
    import "google/protobuf/empty.proto";
    import "google/protobuf/duration.proto";
    
    option java_outer_classname = "DaprProtos";
    option java_package = "io.dapr";
    
    option csharp_namespace = "Dapr.Client.Grpc";
    
    
    // Dapr definitions
    service Dapr {
    rpc PublishEvent(PublishEventEnvelope) returns (google.protobuf.Empty) {}
    rpc InvokeService(InvokeServiceEnvelope) returns (InvokeServiceResponseEnvelope) {}
    rpc InvokeBinding(InvokeBindingEnvelope) returns (google.protobuf.Empty) {}
    rpc GetState(GetStateEnvelope) returns (GetStateResponseEnvelope) {}
    rpc SaveState(SaveStateEnvelope) returns (google.protobuf.Empty) {}
    rpc DeleteState(DeleteStateEnvelope) returns (google.protobuf.Empty) {}
    }
    
    message InvokeServiceResponseEnvelope {
    google.protobuf.Any data = 1;
    map<string,string> metadata = 2;
    }
    
    message DeleteStateEnvelope {
    string key = 1;
    string etag = 2;
    StateOptions options = 3;
    }
    
    message SaveStateEnvelope {
    repeated StateRequest requests = 1;
    }
    
    message GetStateEnvelope {
        string key = 1;
        string consistency = 2;
    }
    
    message GetStateResponseEnvelope {
    google.protobuf.Any data = 1;
    string etag = 2;
    }
    
    message InvokeBindingEnvelope {
    string name = 1;
    google.protobuf.Any data = 2;
    map<string,string> metadata = 3;
    }
    
    message InvokeServiceEnvelope {
    string id = 1;
    string method = 2;
    google.protobuf.Any data = 3;
    map<string,string> metadata = 4;
    }
    
    message PublishEventEnvelope {
        string topic = 1;
        google.protobuf.Any data = 2;
    }
    
    message State {
    string key = 1;
    google.protobuf.Any value = 2;
    string etag = 3;
    map<string,string> metadata = 4;
    StateOptions options = 5;
    }
    
    message StateOptions {
    string concurrency = 1;
    string consistency = 2;
    RetryPolicy retryPolicy = 3;
    }
    
    message RetryPolicy {
    int32 threshold = 1;
    string pattern = 2;
    google.protobuf.Duration interval = 3;
    }
    
    message StateRequest {
    string key = 1;
    google.protobuf.Any value = 2;
    string etag = 3;
    map<string,string> metadata = 4;
    StateRequestOptions options = 5;
    }
    
    message StateRequestOptions {
    string concurrency = 1;
    string consistency = 2;
    StateRetryPolicy retryPolicy = 3;
    }
    
    message StateRetryPolicy {
    int32 threshold = 1;
    string pattern = 2;
    google.protobuf.Duration interval = 3;
    }
    說明
    • 此文件提供6個 GRPC 服務,此文介紹的函數爲 InvokeService()
      • 請求構造爲 InvokeServiceEnvelope
        • id 請求的服務的 --app-id ,好比 productService
        • method 請求的方法
        • data 請求函數的簽名
        • metadata 元數據鍵值對
  3. 修改 StorageController 中的 InitialStorage() 函數爲

    /// <summary>
    /// 初始化倉庫.
    /// </summary>
    /// <returns>是否成功.</returns>
    [HttpGet("InitialStorage")]
    public async Task<bool> InitialStorage()
    {
        string defaultPort = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT") ?? "5001";
    
        // Set correct switch to make insecure gRPC service calls. This switch must be set before creating the GrpcChannel.
        AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
        // Create Client
        string daprUri = $"http://127.0.0.1:{defaultPort}";
        GrpcChannel channel = GrpcChannel.ForAddress(daprUri);
        var client = new Dapr.Client.Grpc.Dapr.DaprClient(channel);
    
        InvokeServiceResponseEnvelope result = await client.InvokeServiceAsync(new InvokeServiceEnvelope
        {
            Method = "GetAllProducts",
            Id = "productService",
            Data = Any.Pack(new ProductListRequest())
        });
        ProductList.V1.ProductList productResult = ProductList.V1.ProductList.Parser.ParseFrom(result.Data.Value);
    
        var random = new Random();
    
        foreach (Product item in productResult.Results)
        {
            _storageContext.Storage.Add(new Storage
            {
                ProductID = Guid.Parse(item.ID),
                Amount = random.Next(1, 1000)
            });
        }
    
        await _storageContext.SaveChangesAsync();
        return true;
    }
  4. 啓動 StorageService

    dapr run --app-id storageService --app-port 5003 dotnet run
  5. 使用 Postman 請求 StorageService 的 InitialStorage

  6. 使用 MySql Workbench 查看結果

小結
至此,以 Dapr 框架使用 GRPC 客戶端在 StorageService 中完成了對 ProductService 服務的調用。

源碼地址

相關文章
相關標籤/搜索