开始使用 Dapr

提示

此内容摘自电子书《面向 .NET 开发人员的 Dapr》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

《面向 .NET 开发人员的 Dapr》电子书封面缩略图。

在前两章中,你了解了 Dapr 的基本概念。 现在,我们将尝试来进行运用。 本章将指导你完成准备本地开发环境并生成两个 Dapr .NET 应用程序。

将 Dapr 安装到本地环境中

首先,在开发计算机上安装 Dapr。 完成后,可以在自托管模式下生成并运行 Dapr 应用程序。

  1. 安装 Dapr CLI。 它使你能够启动、运行并管理 Dapr 实例。 它还提供调试支持。

  2. 安装 Docker Desktop。 如果在 Windows 上运行,请确保将用于 Windows 的 Docker Desktop 配置为使用 Linux 容器。

    注意

    默认情况下,Dapr 使用 Docker 容器来为你提供最佳的全新体验。 若要在 Docker 外部运行 Dapr,可以跳过此步骤并执行精简初始化。 本章中的示例要求使用 Docker 容器。

  3. 初始化 Dapr。 此步骤通过安装最新的 Dapr 二进制和容器映像来设置开发环境。

  4. 安装 .NET 7 SDK

安装完 Dapr 后,现在可以生成你的第一个 Dapr 应用程序了!

生成你的第一个 Dapr 应用程序

首先,生成一个使用 Dapr 状态管理构建块的简单 .NET 控制台应用程序。

创建应用程序

  1. 打开所选的命令行界面或终端。 可以考虑使用 Visual Studio Code 中的终端功能。 导航到要在其中生成应用程序的根文件夹。 导航到那里后,输入以下命令以创建新的 .NET 控制台应用程序:

    dotnet new console -o DaprCounter
    

    此命令将搭建一个简单“Hello World”.NET 应用程序的基架。

  2. 然后,导航到由上一个命令创建的新目录:

    cd DaprCounter
    
  3. 使用 dotnet run 命令运行新创建的应用程序。 这样会将“Hello World!”写入到控制台屏幕:

    dotnet run
    

添加 Dapr 状态管理

接下来,将使用 Dapr 状态管理构建块在程序中实现一个有状态计数器。

可以使用 Dapr 对 HTTP 和 gRPC 的本机支持在任何开发平台上调用 Dapr API。 不过,.NET 开发人员发现使用 Dapr .NET SDK 更自然且更直观。 它提供强类型的 .NET 客户端来调用 Dapr API。 .NET SDK 还与 ASP.NET Core 紧密集成。

  1. 在终端窗口中,将 Dapr.Client NuGet 包添加到应用程序:

    dotnet add package Dapr.Client
    
  2. 在你最喜爱的编辑器中打开 Program.cs,将它的内容更新为以下代码:

    using Dapr.Client;
    
    const string storeName = "statestore";
    const string key = "counter";
    
    var daprClient = new DaprClientBuilder().Build();
    var counter = await daprClient.GetStateAsync<int>(storeName, key);
    
    while (true)
    {
        Console.WriteLine($"Counter = {counter++}");
    
        await daprClient.SaveStateAsync(storeName, key, counter);
        await Task.Delay(1000);
    }
    

    更新的代码实现以下步骤:

    • 首先,会实例化一个新的 DaprClient 实例。 此类使你能够与 Dapr 挎斗进行交互。
    • 从状态存储中,DaprClient.GetStateAsync 会提取 counter 键的值。 如果该键不存在,会返回 int 的默认值(即 0)。
    • 然后,代码将循环访问,将 counter 值写入控制台并将递增的值保存到状态存储中。
  3. Dapr CLI run 命令将启动应用程序。 它调用基础 Dapr 运行时,并使应用程序和 Dapr 挎斗一起运行。 如果省略 app-id,Dapr 将为应用程序生成唯一的名称。 此命令的最后一段 dotnet run 指示 Dapr 运行时运行 .NET 应用程序。

    重要

    必须要注意的是,使用状态管理构建块时始终传递显式 app-id 参数。 对于每个键/值对,构建块将应用程序 ID 值用作其状态键的前缀。 如果应用程序 ID 发生了更改,则将无法再访问之前存储的状态。

    现在,使用以下命令运行应用程序:

    dapr run --app-id DaprCounter dotnet run
    

    尝试停止并重新启动应用程序。 你将看到计数器未重置。 相反,它会从之前保存的状态继续。 Dapr 构建块使应用程序具有状态。

重要

需要认识到的重要一点是,你的示例应用程序会与预配置的状态组件进行通信,但没有直接依赖它。 Dapr 将抽象出依赖关系。 很快你将会看到,可以通过简单的配置更新来更改基础状态存储组件。

你可能会感到疑惑,状态究竟存储在哪里?

组件配置文件

当你首次为本地环境初始化 Dapr 时,它将自动预配一个 Redis 容器。 然后,Dapr 使用一个名为 statestore.yaml 的组件配置文件将 Redis 容器配置为默认的状态存储组件。 文件的具体内容以下:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"

注意

在 Linux/macOS 上,默认组件配置文件存储在 $HOME/.dapr/components 文件夹中;在 Windows 上,默认组件配置文件存储在 %USERPROFILE%\.dapr\components 文件夹中。

请注意上面所示的组件配置文件的格式:

  • 每个组件都有一个名称。 在上面的示例中,组件名为 statestore。 我们在第一个代码示例中使用了该名称,告知 Dapr 挎斗要使用的组件。
  • 每个组件配置文件都有一个 spec 部分。 此部分包含指定组件类型的 type 字段。 version 字段指定组件版本。 metadata 字段包含组件需要的信息,例如连接详细信息和其他设置。 不同类型的组件的元数据值会有所不同。

Dapr 挎斗可以使用在应用程序中配置的任何 Dapr 组件。 但是,如果你的体系结构限制了组件的可访问性会发生什么情况? 如何将 Redis 组件限制为仅在生产环境中运行的 Dapr 挎斗?

为此,可以为生产环境定义一个 namespace。 可以将它命名为 production。 在自托管模式下,通过设置 NAMESPACE 环境变量来指定 Dapr 挎斗的命名空间。 配置后,Dapr 挎斗将仅加载与该命名空间相匹配的组件。 对于 Kubernetes 部署,Kubernetes 命名空间确定加载的组件。 下面的示例展现了 production 命名空间中放置的 Redis 组件。 请注意 metadata 元素中的 namespace 声明:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
  namespace: production
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"

重要

只有在同一命名空间中运行的应用程序才能访问带命名空间的组件。 如果 Dapr 应用程序无法加载组件,请确保应用程序命名空间与组件命名空间相匹配。 在应用程序命名空间存储在 NAMESPACE 环境变量中的自托管模式下,这一点特别难以确认。

如果需要,可以进一步将组件限制到特定的应用程序。 在 production 命名空间中,可能需要将 Redis 缓存的访问限制为仅限 DaprCounter 应用程序。 为此,请在组件配置中指定 scopes。 下面的示例演示如何将 Redis statestore 组件的访问限制为 production 命名空间中的应用程序 DaprCounter

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
  namespace: production
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"
  scopes:
  - DaprCounter

生成多容器 Dapr 应用程序

在第一个示例中,创建了一个与 Dapr 挎斗并行运行的简单 .NET 控制台应用程序。 不过,新式分散式应用程序通常由许多移动的部件组成。 它们可以同时运行独立的微服务。 这些新式应用程序通常是容器化应用程序,并需要容器业务流程工具,例如 Docker Compose 或 Kubernetes。

在下一个示例中,你将创建一个多容器应用程序。 还将使用 Dapr 服务调用构建块在服务之间进行通信。 解决方案中将包含一个 Web 应用程序,该应用程序从 Web API 检索天气预报。 它们每个都在 Docker 容器中运行。 你将使用 Docker Compose 在本地运行容器,并启用调试功能。

请确保为 Dapr 配置了本地环境并安装了 .NET 7 开发工具(本章的开头提供了相关说明)。

此外,还需要使用安装了 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022 来完成此示例。

创建应用程序

  1. 在 Visual Studio 2022 中,创建一个 ASP.NET Core Web 应用项目:

    创建新 Web 应用项目的屏幕截图

  2. 将你的项目命名为 MyFrontEnd,将你的解决方案命名为 DaprMultiContainer

    配置新 Web 应用项目的屏幕截图

  3. 在最后一个对话框中,保留默认值。 请勿选择“启用 Docker 支持”。 稍后添加 Docker 支持。

    新 Web 应用项目的附加信息屏幕截图

  4. 对于后端,请将一个 ASP.NET Core Web API 项目添加到同一解决方案中:

    创建新 Web API 项目的屏幕截图

  5. 将项目命名为 MyBackEnd

    配置新 Web API 项目的屏幕截图

  6. 默认情况下,Dapr 挎斗依赖于网络边界来限制对其公共 API 的访问。 因此,请清除“为 HTTPS 配置”复选框:

    新 Web API 项目的附加信息屏幕截图

    重要

    如果使“为 HTTPS 配置”复选框保留为选中状态,生成的 ASP.NET Core API 项目将包含用于将客户端请求重定向到 HTTPS 终结点的中间件。 这将中断 Dapr 挎斗与应用程序之间的通信,除非显式配置在运行 Dapr 应用程序时使用 HTTPS。 若要使 Dapr 挎斗通过 HTTPS 进行通信,请将 --app-ssl 标记包含在 Dapr 命令中来启动应用程序。 另外,使用 --app-port 参数来指定 HTTPS 端口。 本演练的其余部分在挎斗和应用程序之间使用纯 HTTP 通信,并要求清除“为 HTTPS 配置”复选框。

添加 Dapr 服务调用

现在,你将使用 Dapr 服务调用构建块配置服务之间的通信。 你将使 Web 应用能从 Web API 中检索天气预报。 服务调用构建块有许多优势。 其中包括服务发现、自动重试、消息加密(使用 mTLS)以及改进的可观测性。 你将使用 Dapr .NET SDK 在 Dapr 挎斗上调用服务调用 API。

  1. 在 Visual Studio 中,打开包管理器控制台(“工具”>“NuGet 包管理器”>“包管理器控制台”),并确保 MyFrontEnd 是默认的项目。 在控制台中,向项目添加 Dapr.AspNetCore NuGet 包:

    Install-Package Dapr.AspNetCore
    
  2. MyFrontEnd 项目中,打开 Program.cs 文件并添加对 builder.Services.AddDaprClient 的调用:

    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddDaprClient();
    builder.Services.AddRazorPages();
    
    // ...
    

    AddDaprClient 调用将 DaprClient 类注册到 ASP.NET Core 依赖关系注入系统。 在注册了客户端后,现在可以将 DaprClient 的实例注入到服务代码中,以便与 Dapr 挎斗、构建块和组件进行通信。

  3. 将一个名为 WeatherForecast 的新的 C# 类文件添加到 MyFrontEnd 项目:

    namespace MyFrontEnd;
    
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
    
        public int TemperatureC { get; set; }
    
        public int TemperatureF { get; set; }
    
        public string Summary { get; set; } = string.Empty;
    }
    
  4. 在“Pages”文件夹中打开“Index.cshtml.cs”文件,并将它的内容替换为以下代码:

    using Dapr.Client;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
    namespace MyFrontEnd.Pages;
    
    public class IndexModel : PageModel
    {
        private readonly DaprClient _daprClient;
    
        public IndexModel(DaprClient daprClient)
        {
            _daprClient = daprClient;
        }
    
        public async Task OnGet()
        {
            var forecasts = await _daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(
                HttpMethod.Get,
                "MyBackEnd",
                "weatherforecast");
    
            ViewData["WeatherForecastData"] = forecasts;
        }
    }
    

    通过将 DaprClient 类注入到 IndexModel 构造函数中,从而将 Dapr 功能添加到 Web 应用中。 在 OnGet 方法中,使用 Dapr 服务调用构建块来调用后端 API 服务。 每当用户访问主页时,都会调用 OnGet 方法。 使用 DaprClient.InvokeMethodAsync 方法来调用 MyBackEnd 服务的 weatherforecast 方法。 稍后,在配置为与 Dapr 一起运行时将 Web API 配置为将 MyBackEnd 作用其应用程序 ID。 最后,服务响应会以视图数据的形式保存。

  5. 将“Pages”文件夹中“Index.cshtml”文件的内容替换为以下代码。 它会向用户显示视图数据中存储的天气预报:

    @page
    @model IndexModel
    @{
        ViewData["Title"] = "Home page";
    }
    
    <div class="text-center">
        <h1 class="display-4">Welcome</h1>
        <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
        @foreach (var forecast in (IEnumerable<WeatherForecast>)ViewData["WeatherForecastData"]!)
        {
            <p>The forecast for @forecast.Date is @forecast.Summary!</p>
        }
    </div>
    

添加容器支持

在此示例的最后一部分,你将添加容器支持,并使用 Docker Compose 运行解决方案。

  1. 右键单击 MyFrontEnd 项目,选择“添加”>“容器业务流程协调程序支持...”。随即将显示“添加容器业务流程协调程序支持”对话框:

    添加容器业务流程协调程序支持的屏幕截图

    选择“Docker Compose”

  2. 在下一个对话框中,选择“Linux”作为目标 OS:

    选择 Docker 目标 OS 的屏幕截图

    Visual Studio 在解决方案中的 docker-compose 文件夹中创建一个 docker-compose.yml 文件和一个 .dockerignore 文件:

    docker-compose 项目的屏幕截图

    docker-compose.yml 文件包含以下内容:

    version: '3.4'
    
    services:
    myfrontend:
        image: ${DOCKER_REGISTRY-}myfrontend
        build:
        context: .
        dockerfile: MyFrontEnd/Dockerfile
    

    .dockerignore 文件包含你不希望 Docker 在容器中包含的文件类型和扩展名。 这些文件与开发环境和源代码管理相关联,而不是与所部署的应用或服务相关联。

    在 MyFrontEnd 项目目录的根目录中,创建了一个新的 Dockerfile。 Dockerfile 是用于生成映像的一系列命令。 有关详细信息,请参阅 Dockerfile 参考

    此 Dockerfile 包含以下命令:

    FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
    WORKDIR /app
    EXPOSE 80
    EXPOSE 443
    
    FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
    WORKDIR /src
    COPY ["MyFrontEnd/MyFrontEnd.csproj", "MyFrontEnd/"]
    RUN dotnet restore "MyFrontEnd/MyFrontEnd.csproj"
    COPY . .
    WORKDIR "/src/MyFrontEnd"
    RUN dotnet build "MyFrontEnd.csproj" -c Release -o /app/build
    
    FROM build AS publish
    RUN dotnet publish "MyFrontEnd.csproj" -c Release -o /app/publish
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "MyFrontEnd.dll"]
    

    在调用时,上述的 Dockerfile 将按顺序执行以下步骤:

    1. 拉取 mcr.microsoft.com/dotnet/aspnet:7.0 映像并将它命名为 base
    2. 将工作目录设置为 /app。
    3. 公开端口 80443
    4. 拉取 mcr.microsoft.com/dotnet/sdk:7.0 映像并将它命名为 build
    5. 将工作目录设置为 /src。
    6. 将 MyFrontEnd/MyFrontEnd.csproj 复制到名为 MyFrontEnd/ 的新目录中。
    7. 在项目中调用 dotnet restore
    8. 将根目录中的所有内容复制到映像的根目录中。
    9. 将工作目录设置为 /src/MyFrontEnd。
    10. 在项目中调用 dotnet build
      • 将 Release 配置和输出定位到 /app/build。
    11. 从现有的 build 基础映像初始化新的生成阶段,并将它命名为 publish
    12. 在项目中调用 dotnet publish
      • 将 Release 配置和输出定位到 /app/publish。
    13. 从现有的 publish 基础映像初始化新的生成阶段,并将它命名为 final
    14. 将工作目录设置为 /app。
    15. /app/publish 目录从 publish 映像复制到 final 映像的根目录中。
    16. 将入口点设置为到 dotnet 的映像,并将 MyFrontEnd.dll 作为参数传递。
  3. MyBackEnd Web API 项目中,右键单击项目节点,选择“添加”>“容器业务流程协调程序支持...”。选择“Docker Compose”,然后再次选择“Linux”作为目标 OS。

    在 MyBackEnd 项目目录的根目录中,创建了一个新的 Dockerfile。 此 Dockerfile 包含以下命令:

    FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
    WORKDIR /app
    EXPOSE 80
    
    FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
    WORKDIR /src
    COPY ["MyBackEnd/MyBackEnd.csproj", "MyBackEnd/"]
    RUN dotnet restore "MyBackEnd/MyBackEnd.csproj"
    COPY . .
    WORKDIR "/src/MyBackEnd"
    RUN dotnet build "MyBackEnd.csproj" -c Release -o /app/build
    
    FROM build AS publish
    RUN dotnet publish "MyBackEnd.csproj" -c Release -o /app/publish
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "MyBackEnd.dll"]
    

    再次打开 docker-compose.yml 文件,并检查它的内容。 Visual Studio 已更新了 Docker Compose 文件。 现在,这两项服务都包含在内:

    version: '3.4'
    
    services:
      myfrontend:
        image: ${DOCKER_REGISTRY-}myfrontend
        build:
          context: .
          dockerfile: MyFrontEnd/Dockerfile
    
      mybackend:
        image: ${DOCKER_REGISTRY-}mybackend
        build:
          context: .
          dockerfile: MyBackEnd/Dockerfile
    
  4. 若要在容器化应用程序中使用 Dapr 构建块,需要将 Dapr 挎斗容器添加到 Compose 文件。 请谨慎地将 docker-compose.yml 文件的内容更新为与以下示例相匹配。 请密切注意格式和间距,切勿使用制表符。

    version: '3.4'
    
    services:
      myfrontend:
        image: ${DOCKER_REGISTRY-}myfrontend
        build:
          context: .
          dockerfile: MyFrontEnd/Dockerfile
        ports:
          - "51000:50001"
    
      myfrontend-dapr:
        image: "daprio/daprd:latest"
        command: [ "./daprd", "-app-id", "MyFrontEnd", "-app-port", "80" ]
        depends_on:
          - myfrontend
        network_mode: "service:myfrontend"
    
      mybackend:
        image: ${DOCKER_REGISTRY-}mybackend
        build:
          context: .
          dockerfile: MyBackEnd/Dockerfile
        ports:
          - "52000:50001"
    
      mybackend-dapr:
        image: "daprio/daprd:latest"
        command: [ "./daprd", "-app-id", "MyBackEnd", "-app-port", "80" ]
        depends_on:
          - mybackend
        network_mode: "service:mybackend"
    

    在更新后的文件中,我们分别为 myfrontendmybackend 服务添加了 myfrontend-daprmybackend-dapr 挎斗。 在更新后的文件中,请密切注意以下更改:

    • 挎斗使用了 daprio/daprd:latest 容器映像。 不建议将 latest 标记用于生产方案。 对于生产,最好的做法是使用特定的版本号。
    • 出于网络隔离目的,Compose 文件中定义的每个服务都有其自己的网络命名空间。 挎斗使用 network_mode: "service:..." 确保它们在与应用程序所在的同一网络命名空间中运行。 这样做能够使挎斗和应用程序使用 localhost 进行通信。
    • 必须公开 Dapr 挎斗在上面侦听 gRPC 通信的端口(默认为 50001),从而使挎斗能够彼此通信。
  5. 运行解决方案(按 F5 或 Ctrl+F5)以验证它是否按预期正常工作。 如果一切配置正确,你应该会看到天气预报数据:

    显示天气预报数据的最终解决方案的屏幕截图

    通过使用 Docker Compose 和 Visual Studio 在本地运行,可以将断点和调试设置到应用程序中。 对于生产方案,建议在 Kubernetes 中托管应用程序。 本书包括随附的参考应用程序 eShopOnDapr,其中包含要部署到 Kubernetes 的脚本。

    若要详细了解有关本演练中使用的 Dapr 服务调用构建块,请参阅第 6 章

摘要

在本章中,你有机会体验了使用 Dapr。 通过使用 Dapr .NET SDK,了解了 Dapr 如何与 .NET 应用程序平台相集成。

第一个示例是一个使用 Dapr 状态管理构建块的简单的有状态 .NET 控制台应用程序。

第二个示例涉及到在 Docker 中运行的一个多容器应用程序。 通过将 Visual Studio 与 Docker Compose 结合使用,可以在所有 .NET 应用中使用熟悉的 F5 调试体验。

我们还深入介绍了 Dapr 组件配置文件。 它们配置由 Dapr 构建块使用的实际基础结构实现。 可以使用命名空间和范围将组件访问限制为特定的环境和应用程序。

在接下来的章节中,你将深入了解 Dapr 提供的构建块。

参考