DaprClient 使用

使用 DaprClient 的基本提示和建议

生命周期管理

DaprClient 使用 TCP 套接字来访问网络资源,与 Dapr sidecar 进行通信。它实现了 IDisposable 接口,以便快速清理资源。

依赖注入

通过 AddDaprClient() 方法可以在 ASP.NET Core 中注册 Dapr 客户端。此方法接受一个可选的配置委托,用于配置 DaprClient,以及一个 ServiceLifetime 参数,允许您为注册的资源指定不同的生命周期,默认是 Singleton。

以下示例展示了如何使用默认值注册 DaprClient:

services.AddDaprClient();

您可以通过配置委托在 DaprClientBuilder 上指定选项来配置 DaprClient,例如:

services.AddDaprClient(daprBuilder => {
    daprBuilder.UseJsonSerializerOptions(new JsonSerializerOptions {
            WriteIndented = true,
            MaxDepth = 8
        });
    daprBuilder.UseTimeout(TimeSpan.FromSeconds(30));
});

另一个重载允许访问 DaprClientBuilder 和 IServiceProvider,以便进行更高级的配置,例如从依赖注入容器中获取服务:

services.AddSingleton<SampleService>();
services.AddDaprClient((serviceProvider, daprBuilder) => {
    var sampleService = serviceProvider.GetRequiredService<SampleService>();
    var timeoutValue = sampleService.TimeoutOptions;
    
    daprBuilder.UseTimeout(timeoutValue);
});

手动实例化

除了依赖注入,您还可以使用静态客户端构建器手动创建 DaprClient。

为了优化性能,建议创建一个长生命周期的 DaprClient 实例,并在整个应用程序中共享。DaprClient 是线程安全的,适合共享使用。

避免为每个操作创建一个新的 DaprClient 实例并在操作完成后释放它。

配置 DaprClient

在调用 .Build() 创建客户端之前,可以通过 DaprClientBuilder 类上的方法来配置 DaprClient。每个 DaprClient 对象的设置是独立的,创建后无法更改。

var daprClient = new DaprClientBuilder()
    .UseJsonSerializerSettings( ... ) // 配置 JSON 序列化器
    .Build();

默认情况下,DaprClientBuilder 会按以下顺序优先获取配置值:

  • 直接提供给 DaprClientBuilder 方法的值(例如 UseTimeout(TimeSpan.FromSeconds(30)))
  • 从可选的 IConfiguration 中提取的值,与环境变量名称匹配
  • 从环境变量中提取的值
  • 默认值

在 DaprClientBuilder 上配置

DaprClientBuilder 提供以下方法来设置配置选项:

  • UseHttpEndpoint(string): 设置 Dapr sidecar 的 HTTP 端点
  • UseGrpcEndpoint(string): 设置 Dapr sidecar 的 gRPC 端点
  • UseGrpcChannelOptions(GrpcChannelOptions): 设置 gRPC 通道选项
  • UseHttpClientFactory(IHttpClientFactory): 配置 DaprClient 使用的 HttpClient 工厂
  • UseJsonSerializationOptions(JsonSerializerOptions): 配置 JSON 序列化
  • UseDaprApiToken(string): 为 Dapr sidecar 的身份验证提供令牌
  • UseTimeout(TimeSpan): 指定与 Dapr sidecar 通信时的超时值

从 IConfiguration 配置

除了直接从环境变量获取配置值,您还可以通过 IConfiguration 提供这些值。

例如,在多租户环境中,您可能需要为环境变量添加前缀。以下示例展示了如何从环境变量中获取这些值到 IConfiguration,并移除前缀:

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables("test_"); // 获取所有以 "test_" 开头的环境变量,并移除前缀
builder.Services.AddDaprClient();

从环境变量配置

SDK 会读取以下环境变量来配置默认值:

  • DAPR_HTTP_ENDPOINT: Dapr sidecar 的 HTTP 端点,例如:https://dapr-api.mycompany.com
  • DAPR_GRPC_ENDPOINT: Dapr sidecar 的 gRPC 端点,例如:https://dapr-grpc-api.mycompany.com
  • DAPR_HTTP_PORT: 如果未设置 DAPR_HTTP_ENDPOINT,则用于查找本地 HTTP 端点
  • DAPR_GRPC_PORT: 如果未设置 DAPR_GRPC_ENDPOINT,则用于查找本地 gRPC 端点
  • DAPR_API_TOKEN: 设置 API 令牌

配置 gRPC 通道选项

Dapr 使用 CancellationToken 进行取消,依赖于 gRPC 通道选项的配置,默认已启用。如果您需要自行配置这些选项,请确保启用 ThrowOperationCanceledOnCancellation 设置。

var daprClient = new DaprClientBuilder()
    .UseGrpcChannelOptions(new GrpcChannelOptions { ... ThrowOperationCanceledOnCancellation = true })
    .Build();

使用 DaprClient 进行取消

在 DaprClient 上执行异步操作的 API 接受一个可选的 CancellationToken 参数。这遵循 .NET 的标准惯例,用于可取消的操作。请注意,当取消发生时,不能保证远程端点停止处理请求,只能保证客户端已停止等待完成。

当操作被取消时,将抛出一个 OperationCancelledException。

理解 DaprClient 的 JSON 序列化

DaprClient 上的许多方法使用 System.Text.Json 序列化器执行 JSON 序列化。接受应用程序数据类型作为参数的方法将对其进行 JSON 序列化,除非文档明确说明了其他情况。

如果您有高级需求,建议阅读 System.Text.Json 文档。Dapr .NET SDK 不提供独特的序列化行为或自定义 - 它依赖于底层序列化器将数据转换为和从应用程序的 .NET 类型。

DaprClient 被配置为使用从 JsonSerializerDefaults.Web 配置的序列化器选项对象。这意味着 DaprClient 将使用 camelCase 作为属性名称,允许读取带引号的数字("10.99"),并将不区分大小写地绑定属性。这些是与 ASP.NET Core 和 System.Text.Json.Http API 一起使用的相同设置,旨在遵循可互操作的 Web 约定。

截至 .NET 5.0,System.Text.Json 对所有 F# 语言特性内置支持不佳。如果您使用 F#,您可能需要使用一个添加对 F# 特性支持的转换器包,例如 FSharp.SystemTextJson。

JSON 序列化的简单指导

如果您使用的功能集映射到 JSON 的类型系统,您在使用 JSON 序列化和 DaprClient 时的体验将会很顺利。这些是可以简化代码的通用指南。

  • 避免继承和多态
  • 不要尝试序列化具有循环引用的数据
  • 不要在构造函数或属性访问器中放置复杂或昂贵的逻辑
  • 使用与 JSON 类型(数值类型、字符串、DateTime)清晰映射的 .NET 类型
  • 为顶级消息、事件或状态值创建自己的类,以便将来可以添加属性
  • 设计具有 get/set 属性的类型,或者使用 支持的模式 用于 JSON 的不可变类型

多态性和序列化

DaprClient 使用的 System.Text.Json 序列化器在执行序列化时使用值的声明类型。

本节将使用 DaprClient.SaveStateAsync<TValue>(...) 作为示例,但建议适用于 SDK 暴露的任何 Dapr 构建块。

public class Widget
{
    public string Color { get; set; }
}
...

// 将 Widget 值作为 JSON 存储在状态存储中
Widget widget = new Widget() { Color = "Green", };
await client.SaveStateAsync("mystatestore", "mykey", widget);

在上面的示例中,类型参数 TValue 的类型参数是从 widget 变量的类型推断出来的。这很重要,因为 System.Text.Json 序列化器将根据值的声明类型执行序列化。结果是 JSON 值 { "color": "Green" } 将被存储。

考虑当您尝试使用 Widget 的派生类型时会发生什么:

public class Widget
{
    public string Color { get; set; }
}

public class SuperWidget : Widget
{
    public bool HasSelfCleaningFeature { get; set; }
}
...

// 将 SuperWidget 值作为 JSON 存储在状态存储中
Widget widget = new SuperWidget() { Color = "Green", HasSelfCleaningFeature = true, };
await client.SaveStateAsync("mystatestore", "mykey", widget);

在此示例中,我们使用了一个 SuperWidget,但变量的声明类型是 Widget。由于 JSON 序列化器的行为由声明类型决定,它只看到一个简单的 Widget,并将保存值 { "color": "Green" },而不是 { "color": "Green", "hasSelfCleaningFeature": true }。

如果您希望 SuperWidget 的属性被序列化,那么最好的选择是用 object 覆盖类型参数。这将导致序列化器包含所有数据,因为它对类型一无所知。

Widget widget = new SuperWidget() { Color = "Green", HasSelfCleaningFeature = true, };
await client.SaveStateAsync<object>("mystatestore", "mykey", widget);

错误处理

当遇到故障时,DaprClient 上的方法将抛出 DaprException 或其子类。

try
{
    var widget = new Widget() { Color = "Green", };
    await client.SaveStateAsync("mystatestore", "mykey", widget);
}
catch (DaprException ex)
{
    // 处理异常,记录日志,重试等
}

最常见的故障情况将与以下内容相关:

  • Dapr 组件配置不正确
  • 瞬时故障,例如网络问题
  • 无效数据,例如 JSON 反序列化失败

在任何这些情况下,您都可以通过 .InnerException 属性检查更多异常详细信息。