Dapr
提供了一组构建块,用于抽象
分布式系统中
常用的概念。这包括服务、缓存、工作流、复原能力、机密管理等之间的安全同步和异步通信。不必自己实现这些功能
,可以消除样板
,
降低复杂性
,并允许您
专注于开发业务功能。
在您的时间有限并且您只想进行实验的情况下,在Dapr初始设置上花费大量时间可能会令人沮丧。更不用说你尚未确定对本地开发体验(故障排除、调试、载入等)的影响。也许你的一些同事最初会不情愿,并认为你正在让他们的工作比现在更复杂。
本文将向你展示如何将 Dapr 与
.NET Aspire
结合使用,以获得无与伦比的本地开发体验
。我们将创建一些 ASP.NET 核心和Node.js服务,这些服务将利用
服务调用
、
状态管理和
发布/订阅
。好处是:
使用 .NET Aspire for Dapr 将减少开发人员
的入门时间
。他们可以专注于使用 Dapr 进行功能开发,并花更少的时间设置本地环境。由于与 OpenTelemetry 的集成,可以更轻松地在本地对多个应用程序之间的交互进行故障排除,这通常是在部署代码后在云环境中才能获得的。
使用 .NET Aspire 进行 Dapr 实验的目标是创建
三个服务和 .NET Aspire 主机项目
,后者充当业务流程协调程序:
可在此
GitHub 存储库
找到可以使用
的完整代码
.自述文件将指导您安装必备组件并启动服务。下面的代码是 .NET Aspire 主机项目,我们在其中声明这些服务、Dapr 组件及其关系,不涉及 YAML:
using Aspire.Hosting.Dapr;
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var stateStore = builder.AddDaprStateStore(“statestore”);
var pubSub = builder.AddDaprPubSub(“pubsub”);
builder.AddProject<Projects.AspireDaprDemo_AliceService>(“alice”)
.WithDaprSidecar(new DaprSidecarOptions
{
// Loads the resiliency policy for service invocation from alice to bob
ResourcesPaths = [Path.Combine(“..”, “resources”)],
})
.WithReference(stateStore)
.WithReference(pubSub);
builder.AddProject<Projects.AspireDaprDemo_BobService>(“bob”)
.WithDaprSidecar()
.WithReference(stateStore)
.WithReference(pubSub);
builder.AddNpmApp(“carol”, Path.Combine(“..”, “AspireDaprDemo.CarolService”), “watch”)
.WithHttpEndpoint(port: 3000, env: “PORT”)
.WithEnvironment(“NODE_TLS_REJECT_UNAUTHORIZED”, builder.Environment.IsDevelopment() ? “0” : “1”)
.WithDaprSidecar()
.WithReference(stateStore)
.WithReference(pubSub);
builder.Build().Run();
启动后,Aspire 会启动所有服务,并在仪表板中提供
分布式系统的完整视图
:
在此示例中,Alice 服务公开触发上述交互的终结点。调用此终结点时,OpenTelemetry 跟踪如下所示:
/weatherforecast
加入开发团队的开发人员可以快速了解分布式系统的不同组件如何相互交互。在此屏幕截图中,我们可以看到 flky Bob 服务返回错误,并且 Dapr 自动重试该操作。与 Dapr 提供的默认 Zipkin 实例相比,.NET Aspire 提供了一种更好的方法来可视化 OpenTelemetry 跟踪,因为跟踪不仅在视觉上更清晰,而且仪表板还包括日志和指标。
通常,要配置 Dapr,您需要创建 YAML 配置文件来描述应用程序、sidecar 和网络详细信息(如 TCP 端口)。对于 .NET Aspire,这不是必需的。
Alice 和 Bob 之间的通信(他们的名字是在 Aspire 主机项目中声明的)非常简单,这要归功于 Dapr SDK。
// Otherwise, get a fresh weather forecast from the flaky service “bob” and cache it
var forecasts = await client.InvokeMethodAsync<WeatherForecast[]>(HttpMethod.Get, “bob”, “weatherforecast”);
未在
appsettings.json和
环境变量中配置 URL。使用服务名称
bob
是唯一必需的常量。Dapr 负责将请求路由到正确的服务。
状态存储和 pub/sub 也是如此。只有 Dapr sidecar 知道连接详细信息,因此应用程序无需担心它们。这避免了繁琐的配置文件管理。想象一下,在分布式系统中有 10 个服务,以及 4 个环境:本地环境、dev 环境、stg 环境和 prod。这可能表示要维护的 40 个潜在配置文件,以及数十个 URL 和连接字符串。多亏了 Dapr,您再也不用担心这个问题了。
使用状态存储和 pub/sub 同样简单:
// Retrieve the weather forecast from the state store “statestore” declared in the Aspire host
var cachedForecasts = await client.GetStateAsync<CachedWeatherForecast>(“statestore”, “cache”);
// […]
// Save the weather forecast in the state store under the key “cache”
await client.SaveStateAsync(“statestore”, “cache”, new CachedWeatherForecast(forecasts, DateTimeOffset.UtcNow));
// Publish an event “WeatherForecastMessage” to the pub/sub “pubsub” declared in the Aspire host, with the topic “weather”
await client.PublishEventAsync(“pubsub”, “weather”, new WeatherForecastMessage(“Weather forecast requested!”));
这是订阅“天气”主题的 Carol 服务的片段。请记住,.NET Aspire 和 Dapr 都与语言无关:
// Events are received through HTTP POST requests (push delivery model)
app.post(“/subscriptions/weather”, (req, res) => {
console.log(“Weather forecast message received:”, req.body.data);
res.sendStatus(200);
});
.NET Aspire 在资源上使用
WithDaprSidecar
dapr,
该方法指示 启动可执行文件的实例。
// […]
.WithDaprSidecar()
.WithReference(stateStore)
.WithReference(pubSub);
Dapr传递的参数取决于服务引用的组件数以及在调用上述方法期间可能传递的选项。
这里要记住两个关键点:
如果您想了解详细信息,可以在 .NET Aspire 源代码中的
DaprDistributedApplicationLifecycleHook
类中看它是如何实现的。随后,编排的应用程序被传递环境变量,允许 Dapr SDK 与 sidecar 进行通信。这可以从 Aspire 仪表板上的资源详细信息中看出:
在此实验中,我们使用了 .NET Aspire 本机支持的两个 Dapr 组件。但是,可以使用以下方法AddDaprComponent 声明其他类型的组件:
var stateStore = builder.AddDaprStateStore(“statestore”);
var pubSub = builder.AddDaprPubSub(“pubsub”);
还可以声明资源,例如弹性策略,并将它们分配给 sidecar:
builder.AddProject<Projects.AspireDaprDemo_AliceService>(“alice”)
.WithDaprSidecar(new DaprSidecarOptions
{
// Loads the resiliency policy for service invocation from alice to bob
ResourcesPaths = [Path.Combine(“..”, “resources”)],
})
.WithReference(stateStore)
.WithReference(pubSub);
builder.AddProject<Projects.AspireDaprDemo_BobService>(“bob”)
.WithDaprSidecar()
.WithReference(stateStore)
.WithReference(pubSub);
builder.AddNpmApp(“carol”, Path.Combine(“..”, “AspireDaprDemo.CarolService”), “watch”)
.WithHttpEndpoint(port: 3000, env: “PORT”)
.WithEnvironment(“NODE_TLS_REJECT_UNAUTHORIZED”, builder.Environment.IsDevelopment() ? “0” : “1”)
.WithDaprSidecar()
.WithReference(stateStore)
.WithReference(pubSub);