if 函数 asp_Dapr 运用之集成 Asp.Net Core Grpc 调用篇

前置条件: 《Dapr 运用》


改造 ProductService 以提供 gRPC 服务

  1. 从 NuGet 或程序包管理控制台安装 gRPC 服务必须的包

  • Grpc.AspNetCore

配置 Http/2

  • gRPC 服务需要 Http/2 协议

    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();
    });
    }

新建了 product.proto 以定义 GRPC 服务,它需要完成的内容是返回所有产品集合,当然目前产品内容只有一个 ID

  • 定义产品列表 gRPC 服务,得益于宇宙第一 IDE Visual Studio ,只要添加 Grpc.Tools 包就可以自动生成 gRPC 所需的代码,这里不再需要手动去添加 Grpc.Tools ,官方提供的 Grpc.AspNetCore 中已经集成了

  • 定义了一个服务 ProductRPCService

  • 定义了一个函数 ProductRPCService

  • 定义了一个请求构造 ProductListRequest ,内容为空

  • 定义了一个请求返回构造 ProductList ,使用 repeated 表明返回数据是集合

  • 定义了一个数据集合中的一个对象 Product

  • 定义产品 proto

    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;
    }

    说明

  • 添加 ProductListService 文件,内容如下

        public class ProductListService : ProductRPCService.ProductRPCServiceBase
    {
    private readonly ProductContext _productContext;

    public ProductListService(ProductContext productContext){
    _productContext = productContext;
    }

    public override async Task GetAllProducts(ProductListRequest request, ServerCallContext context)
    {
    IList 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;
    }
    }

在 Startup.cs 修改代码如下

public void ConfigureServices(IServiceCollection services){
//启用 gRPC 服务
services.AddGrpc();
services.AddTransient();
...
}

这里的 services.AddTransient(); 的原因是在 Dapr 中需要使用构造器注入,以完成 GetAllProducts(...) 函数的调用

public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
...

//添加 gRPC 到路由管道中
endpoints.MapGrpcService();
});
}

这里添加的代码的含义分别是启用 gRPC 服务和添加 gRPC 路由。得益于 ASP.NET Core 中间件的优秀设计,ASP.NET Core 可同时支持 Http 服务。

添加 daprclient.proto 文件以生成 Dapr Grpc 服务,daprclient.proto 内容如下

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;
}

说明

  • method 提供调用方法名称

  • data 请求数据

  • metadata 额外数据,此处使用键值对形式体现

  • 此文件为官方提供,Dapr 0.3 版本之前提供的已经生成好的代码,现在看源码可以看出已经改为提供 proto 文件了,这里我认为提供 proto 文件比较合理

  • 此文件定义了5个函数,此文主要讲的就是 OnInvoke() 函数

  • OnInvoke() 请求构造为 InvokeEnvelope

创建 DaprClientService.cs 文件,此文件用于终结点路由,内容为

public class DaprClientService : DaprClient.DaprClientBase
{
private readonly ProductListService _productListService;

///
/// Initializes a new instance of the class.
///
///
public DaprClientService(ProductListService productListService){
_productListService = productListService;
}

public override async TaskOnInvoke(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() 打包需要返回的数据

运行 productService

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

小结
至此,ProductService 服务完成。此时 ProductService.Api.csproj Protobuf 内容为

改造 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;
    }

    说明

  • 请求构造为 InvokeServiceEnvelope

  • id 请求的服务的 --app-id ,比如 productService

  • method 请求的方法

  • data 请求函数的签名

  • metadata 元数据键值对

  • 此文件提供6个 GRPC 服务,此文介绍的函数为 InvokeService()

修改 StorageController 中的 InitialStorage() 函数为

/// 
/// 初始化仓库.
///
/// 是否成功.
[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;
}

启动 StorageService

dapr run --app-id storageService --app-port 5003 dotnet run

使用 Postman 请求 StorageService 的 InitialStorage44ebf7172b6ede9c5949cc5f7f3fa02f.png

使用 MySql Workbench 查看结果02834c11ecb677fe24ad60d954ed334e.png

小结
至此,以 Dapr 框架使用 GRPC 客户端在 StorageService 中完成了对 ProductService 服务的调用。

源码地址: https://github.com/SoMeDay-Zhang/DaprDemos/tree/master/dotnetcore 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值