欢迎使用 Dapr 入门指南!
Dapr Concepts
如果您正在寻找 Dapr 的入门概述并希望了解更多 Dapr 基本术语,我们建议从概念部分开始。我们的入门指南将引导您完成一系列步骤,包括安装、初始化、试验并开始使用 Dapr。
第一步:安装 Dapr CLI >>
This is the multi-page printable view of this section. Click here to print.
欢迎使用 Dapr 入门指南!
我们的入门指南将引导您完成一系列步骤,包括安装、初始化、试验并开始使用 Dapr。
您将使用 Dapr CLI 作为各种 Dapr 相关任务的主要工具。您可以使用它来:
Dapr CLI 同时支持 自托管 和 Kubernetes 环境。

将最新的 Linux Dapr CLI 安装到 /usr/local/bin:
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash -s 1.18.0
sudo 安装如果您没有 sudo 命令的访问权限,或者您的用户名不在 sudoers 文件中,您可以通过 DAPR_INSTALL_DIR 环境变量将 Dapr 安装到备用目录。该目录必须已存在且当前用户可访问。
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | DAPR_INSTALL_DIR="$HOME/dapr" /bin/bash
sudo 安装特定 CLI 版本以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | DAPR_INSTALL_DIR="$HOME/dapr" /bin/bash -s 1.18.0
将最新的 Windows Dapr CLI 安装到 $Env:SystemDrive\dapr,并将此目录添加到用户 PATH 环境变量:
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
注意: PATH 更新可能需要重启终端应用程序后才能生效。
以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
powershell -Command "$script=iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1; $block=[ScriptBlock]::Create($script); invoke-command -ScriptBlock $block -ArgumentList 1.18.0"
如果您没有管理员权限,可以通过 DAPR_INSTALL_DIR 环境变量将 Dapr 安装到备用目录。如果目录不存在,以下脚本会创建该目录。
$Env:DAPR_INSTALL_DIR = "<your_alt_install_dir_path>"
$script=iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1; $block=[ScriptBlock]::Create($script); invoke-command -ScriptBlock $block -ArgumentList "", "$Env:DAPR_INSTALL_DIR"
以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
$Env:DAPR_INSTALL_DIR = "<your_alt_install_dir_path>"
$script=iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1; $block=[ScriptBlock]::Create($script); invoke-command -ScriptBlock $block -ArgumentList "1.18.0", "$Env:DAPR_INSTALL_DIR"
将最新的 Windows Dapr CLI 安装到 $Env:SystemDrive\dapr,并将此目录添加到用户 PATH 环境变量:
winget install Dapr.CLI
预览版本:
安装最新的预览版本:
winget install Dapr.CLI.Preview
每个 Dapr CLI 版本都包含 Windows 安装程序。您可以手动下载 MSI:
dapr.msi。$Env:SystemDrive\dapr。Install 开始安装。安装完成后,您会看到最终消息。将最新的 Darwin Dapr CLI 安装到 /usr/local/bin:
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash
以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash -s 1.18.0
对于 ARM64 Mac:
从终端安装时,提供原生 ARM64 二进制文件。
要安装 Rosetta 模拟:
softwareupdate --install-rosetta
通过 Homebrew 安装:
brew install dapr/tap/dapr-cli
对于 ARM64 Mac:
对于 ARM64 Mac,支持 Homebrew 3.0 及更高版本。请将 Homebrew 更新到 3.0.0 或更高版本,然后运行以下命令:
arch -arm64 brew install dapr/tap/dapr-cli
sudo 安装如果您没有 sudo 命令的访问权限,或者您的用户名不在 sudoers 文件中,您可以通过 DAPR_INSTALL_DIR 环境变量将 Dapr 安装到备用目录。该目录必须已存在且当前用户可访问。
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | DAPR_INSTALL_DIR="$HOME/dapr" /bin/bash
sudo 安装特定 CLI 版本以下示例展示如何安装 CLI 版本 1.18.0。您也可以通过指定版本号来安装候选版本(例如 1.10.0-rc.3)。
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | DAPR_INSTALL_DIR="$HOME/dapr" -s 1.18.0
每个 Dapr CLI 版本都包含各种操作系统和架构。您可以手动下载并安装这些二进制版本。
/usr/local/bin。C:\dapr 的目录。通过重启终端/命令提示符并运行以下命令来验证 CLI 是否已安装:
dapr -h
输出:
__
____/ /___ _____ _____
/ __ / __ '/ __ \/ ___/
/ /_/ / /_/ / /_/ / /
\__,_/\__,_/ .___/_/
/_/
===============================
分布式应用运行时
Usage:
dapr [command]
Available Commands:
completion 生成 shell 自动补全脚本
components 列出所有 Dapr 组件。支持平台:Kubernetes
configurations 列出所有 Dapr 配置。支持平台:Kubernetes
dashboard 启动 Dapr 仪表板。支持平台:Kubernetes 和自托管
help 显示任意命令的帮助信息
init 在支持的托管平台上安装 Dapr。支持平台:Kubernetes 和自托管
invoke 调用给定 Dapr 应用的方法。支持平台:自托管
list 列出所有 Dapr 实例。支持平台:Kubernetes 和自托管
logs 获取应用的 Dapr 边车日志。支持平台:Kubernetes
mtls 检查是否启用了 mTLS。支持平台:Kubernetes
publish 发布发布订阅事件。支持平台:自托管
run 并排运行 Dapr 和(可选的)您的应用。支持平台:自托管
status 显示 Dapr 服务的健康状态。支持平台:Kubernetes
stop 停止 Dapr 实例及其关联的应用。支持平台:自托管
uninstall 卸载 Dapr 运行时。支持平台:Kubernetes 和自托管
upgrade 升级集群中的 Dapr 控制平面安装。支持平台:Kubernetes
version 打印 Dapr 运行时和 CLI 版本
Flags:
-h, --help dapr 的帮助信息
-v, --version dapr 的版本
使用 "dapr [command] --help" 获取有关命令的更多信息。
dapr init 获取 Dapr 边车二进制文件并在本地安装既然您已经安装了 Dapr CLI,请使用该 CLI 在您的本地机器上初始化 Dapr。
Dapr 作为边车与您的应用程序一起运行。在自托管模式下,这意味着它是您本地机器上的一个进程。通过初始化 Dapr,您可以:
Dapr 初始化包括:
推荐的开发环境需要 Docker。虽然您可以不依赖 Docker 来初始化 Dapr,但本指南的后续步骤假定使用推荐的 Docker 开发环境。
您也可以安装 Podman 来代替 Docker。阅读更多关于使用 Podman 初始化 Dapr的信息。
如果满足以下条件,您将需要在此快速入门中使用 sudo:
sudo 运行 Docker 命令,或/usr/local/bin(默认安装路径)。以管理员身份运行 Windows Terminal 或命令提示符。
安装最新的 Dapr 运行时二进制文件:
dapr init
如果您使用 sudo 运行 Docker 命令,则需要使用:
sudo dapr init
如果您在带有 Docker 的 Mac OS Silicon 上安装,可能需要执行以下变通方法,以便在不使用 Kubernetes 的情况下启用 dapr init 与 Docker 通信。
安装最新的 Dapr 运行时二进制文件:
dapr init
预期输出:

如果您遇到有关 Docker 未安装或运行的任何错误消息,请查看故障排除指南。
dapr --version
输出:
CLI version: 1.18.0Runtime version: 1.18.0
如前所述,dapr init 命令会启动多个容器,帮助您开始使用 Dapr。验证您是否有运行 daprio/dapr、openzipkin/zipkin 和 redis 镜像的容器实例:
docker ps
输出:

在 dapr init 时,CLI 还会创建一个默认组件文件夹,其中包含多个 YAML 文件,定义了状态存储、发布订阅和 Zipkin。Dapr 边车将读取这些组件并使用:
通过打开组件目录进行验证:
%UserProfile%\.dapr~/.daprls $HOME/.dapr
输出:
bin components config.yaml
您可以使用 PowerShell 或命令行进行验证。如果使用 PowerShell,请运行:
explorer "$env:USERPROFILE\.dapr"
如果使用命令行,请运行:
explorer "%USERPROFILE%\.dapr"
结果:

要在不安装任何默认配置文件或 Docker 容器的情况下安装 CLI,请使用 --slim`` 标志。[了解更多关于 init` 命令及其标志的信息。](https://docs.dapr.io/zh-hans/reference/cli/dapr-init/)
dapr init --slim
如果您计划构建 Dapr Workflow 应用程序,可以安装 Diagrid Dashboard 以在本地开发期间可视化工作流状态:
使用 Docker 启动 Diagrid Dashboard 容器:
docker run -p 8080:8080 ghcr.io/diagridio/diagrid-dashboard:latest
在本指南中,你将通过运行边车并直接调用状态管理 API 来模拟一个应用程序。 在使用 Dapr CLI 运行 Dapr 后,你将:
在我们的概念文档中了解有关状态构建块及其工作原理的更多信息。
dapr run 命令通常运行你的应用程序和 Dapr 边车。在这种情况下,
它仅运行边车,因为你直接与状态管理 API 交互。
启动一个 Dapr 边车,它将为名为 myapp 的空应用程序监听端口 3500:
dapr run --app-id myapp --dapr-http-port 3500
由于上述命令未定义自定义组件文件夹,Dapr 使用在 dapr init 流程 期间创建的默认组件定义。
使用一个对象更新状态。新状态将如下所示:
[
{
"key": "name",
"value": "Bruce Wayne"
}
]
请注意,状态中包含的每个对象都分配了一个值为 name 的 key。你将在下一步中使用该键。
使用以下命令保存一个新的状态对象:
curl -X POST -H "Content-Type: application/json" -d '[{ "key": "name", "value": "Bruce Wayne"}]' http://localhost:3500/v1.0/state/statestore
Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{ "key": "name", "value": "Bruce Wayne"}]' -Uri 'http://localhost:3500/v1.0/state/statestore'
使用键 name 通过状态管理 API 检索你刚刚存储在状态中的对象。在同一终端窗口中,运行以下命令:
curl http://localhost:3500/v1.0/state/statestore/name
Invoke-RestMethod -Uri 'http://localhost:3500/v1.0/state/statestore/name'
查看 Redis 容器并验证 Dapr 是否将其用作状态存储。使用以下命令通过 Redis CLI 进行操作:
docker exec -it dapr_redis redis-cli
列出 Redis 键以查看 Dapr 如何使用你提供给 dapr run 的 app-id 作为键的前缀来创建键值对:
keys *
输出:1) "myapp||name"
通过运行以下命令查看状态值:
hgetall "myapp||name"
输出:1) "data"2) "\"Bruce Wayne\""3) "version"4) "1"
使用以下命令退出 Redis CLI:
exit
在同一终端窗口中,从状态存储中删除 name 状态对象。
curl -v -X DELETE -H "Content-Type: application/json" http://localhost:3500/v1.0/state/statestore/name
Invoke-RestMethod -Method Delete -ContentType 'application/json' -Uri 'http://localhost:3500/v1.0/state/statestore/name'
既然您已经初始化了 Dapr 并尝试了 Dapr 的一些构建块,请浏览我们更详细的教程。
感谢我们庞大的 Dapr 社区,我们在 Dapr 文档和 GitHub 仓库上提供教程。
| Dapr 文档教程 | 描述 |
|---|---|
| 定义组件 | 创建组件定义文件以与 Secrets 构建块交互。 |
| 配置状态与发布订阅 | 为 Dapr 配置状态存储和发布订阅消息代理组件。 |
| GitHub 教程 | 描述 |
|---|---|
| Hello World | 推荐 演示如何本地运行 Dapr。重点展示服务调用和状态管理。 |
| Hello World Kubernetes | 推荐 演示如何在 Kubernetes 中运行 Dapr。重点展示服务调用和状态管理。 |
| Distributed Calculator | 演示一个分布式计算器应用程序,使用 Dapr 服务驱动 React Web 应用。重点展示多语言编程、服务调用和状态管理。 |
| Pub/Sub | 演示如何使用 Dapr 启用发布订阅应用程序。使用 Redis 作为发布订阅组件。 |
| Bindings | 演示如何使用 Dapr 创建与其他组件的输入和输出绑定。使用与 Kafka 的绑定。 |
| Observability | 演示 Dapr 追踪能力。使用 Zipkin 作为追踪组件。 |
| Secret Store | 演示使用 Dapr Secrets API 访问密钥存储。 |
在构建应用时,你很可能需要创建自己的组件文件定义,具体取决于你想要使用的构建块和特定组件。
在本教程中,你将创建一个组件定义文件来与机密构建块 API交互:
创建一个名为 my-components 的新目录来保存新的机密和组件文件:
mkdir my-components
进入该目录。
cd my-components
Dapr 支持多种类型的机密存储,但在本教程中,创建一个名为 mysecrets.json 的本地 JSON 文件,其中包含以下机密:
{
"my-secret" : "I'm Batman"
}
创建一个新文件 localSecretStore.yaml,内容如下:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: my-secret-store
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: ./mysecrets.json
- name: nestedSeparator
value: ":"
在上述文件定义中:
type: secretstores.local.file 告诉 Dapr 使用本地文件组件作为机密存储。dapr run 的位置。启动一个 Dapr 边车,它将在端口 3500 上监听一个名为 myapp 的空白应用程序:
PowerShell 环境:
dapr run --app-id myapp --dapr-http-port 3500 --resources-path ../
非 PowerShell 环境:
dapr run --app-id myapp --dapr-http-port 3500 --resources-path .
如果出现错误消息,提示 app-id 已被使用,你可能需要停止任何当前正在运行的 Dapr 边车。在运行下一个 dapr run 命令之前,请通过以下方式之一停止边车:
dapr stop 命令。在单独的终端中,运行:
curl http://localhost:3500/v1.0/secrets/my-secret-store/my-secret
Invoke-RestMethod -Uri 'http://localhost:3500/v1.0/secrets/my-secret-store/my-secret'
输出:
{"my-secret":"I'm Batman"}
要开始使用状态和发布订阅构建块,你需要两个组件:
支持的组件完整列表可在此处找到:
在本教程中,我们将介绍如何使用 Redis 快速上手。
Dapr 可以使用任何 Redis 实例,可以是:
如果你已有 Redis 存储,请转到 配置 部分。
在自托管环境中,Dapr CLI 会在初始化过程中自动安装 Redis。你已准备就绪!请跳转到 下一步。
你可以使用 Helm 在 Kubernetes 集群中创建 Redis 实例。开始之前,请先 安装 Helm v3。
将 Redis 安装到你的集群中:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis --set image.tag=6.2
对于 Dapr 的发布订阅功能,你需要至少 Redis 5 版本。对于状态存储,可以使用较低版本。
请注意,在 install 命令中添加 --set architecture=standalone 会创建单副本 Redis 设置,如果你在本地环境中工作,可以节省内存和资源。
运行 kubectl get pods 查看集群中现在运行的 Redis 容器:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-master-0 1/1 Running 0 69s
redis-replicas-0 1/1 Running 0 69s
redis-replicas-1 1/1 Running 0 22s
对于 Kubernetes:
redis-master.default.svc.cluster.local:6379redis 会自动创建。验证你拥有 Azure 订阅。
xxxxxx.redis.cache.windows.net:6380。导航到 设置 下的 访问密钥。
创建一个 Kubernetes secret 来存储你的 Redis 密码:
kubectl create secret generic redis --from-literal=redis-password=*********
从 AWS Redis 部署 Redis 实例。
在 AWS 门户中记下 Redis 主机名,以备后用。
创建一个 Kubernetes secret 来存储你的 Redis 密码:
kubectl create secret generic redis --from-literal=redis-password=*********
从 GCP Cloud MemoryStore 部署 MemoryStore 实例。
在 GCP 门户中记下 Redis 主机名,以备后用。
创建一个 Kubernetes secret 来存储你的 Redis 密码:
kubectl create secret generic redis --from-literal=redis-password=*********
Dapr 通过组件定义用于构建块功能的资源。以下步骤介绍如何将上面创建的资源连接到 Dapr 以用于状态和发布订阅。
在自托管模式下,组件文件自动创建于:
%USERPROFILE%\.dapr\components\$HOME/.dapr/components由于 Kubernetes 文件通过 kubectl 应用,因此可以在任何目录中创建。
创建一个名为 redis-state.yaml 的文件,并粘贴以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: <替换为上面的主机名 - 对于 Kubernetes 上的 Redis 它是 redis-master.default.svc.cluster.local:6379>
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
请注意,上面的代码示例使用了你在设置集群时创建的 Kubernetes secret。
创建一个名为 redis-pubsub.yaml 的文件,并粘贴以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: default
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: default
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: <替换为上面的主机名 - 对于 Kubernetes 上的 Redis 它是 redis-master.default.svc.cluster.local:6379>
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
请注意,上面的代码示例使用了你在设置集群时创建的 Kubernetes secret。
仅用于开发目的,你可以跳过创建 Kubernetes secret 并将密码直接放入 Dapr 组件文件中:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: <HOST>
- name: redisPassword
value: <PASSWORD>
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: default
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: <HOST>
- name: redisPassword
value: <PASSWORD>
# 取消下面的注释以通过 TLS 连接到 redis 缓存实例(例如 - Azure Redis Cache)
# - name: enableTLS
# value: true
当你运行 dapr init 时,Dapr 会在本地机器上创建默认的 redis pubsub.yaml。通过打开你的组件目录进行验证:
%UserProfile%\.dapr\components\pubsub.yaml~/.dapr/components/pubsub.yaml对于新的组件文件:
components 目录,其中包含 YAML 文件。--resources-path 标志向 dapr run 命令提供路径。如果你以 slim 模式(不带 Docker)初始化 Dapr,则需要手动创建默认目录,或始终使用 --resources-path 指定组件目录。
对状态和发布订阅文件运行 kubectl apply -f <FILENAME>:
kubectl apply -f redis-state.yaml
kubectl apply -f redis-pubsub.yaml
通过我们的 Dapr 快速入门快速上手,其中包含旨在帮助您快速上手 Dapr 的代码示例。
| 快速入门 | 描述 |
|---|---|
| 服务调用 | 使用 HTTP 或 gRPC 在两个服务之间进行同步通信。 |
| 发布订阅 | 使用消息在两个服务之间进行异步通信。 |
| 工作流 | 在长期运行的、容错的、有状态的应用程序中编排业务工作流活动。 |
| Agents | 构建 LLM 驱动的自主代理应用程序。 |
| 状态管理 | 将服务的数据以键/值对的形式存储在支持的状态存储中。 |
| Bindings | 使用输入绑定响应事件,使用输出绑定调用操作,从而与外部系统交互。 |
| Actors | 运行微服务和简单的控制台客户端,演示 Dapr Actors 中的有状态对象模式。 |
| Secrets Management | 安全地获取密钥。 |
| Configuration | 获取配置项并订阅配置更新。 |
| Resiliency | 为您的 Dapr API 请求定义并应用容错策略。 |
| Cryptography | 使用 Dapr 的密码学 API 加密和解密数据。 |
| Jobs | 使用 Dapr 的 Jobs API 计划、检索和删除作业。 |
| Conversation | 安全可靠地与大语言模型(LLM)交互。 |
借助 Dapr 的服务调用构建块,您的应用程序可以与其他应用程序进行可靠、安全的通信。

Dapr 为服务调用提供了多种方法,您可以根据场景选择。对于本快速入门,您将使 checkout 服务能够使用 HTTP 代理调用 order-processor 服务中的方法,并通过以下方式之一:
在概述文章中了解更多关于 Dapr 服务调用的方法。
在继续快速入门之前,请选择您的首选语言。
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门克隆目录的根目录,导航到快速入门目录。
cd service_invocation/python/http
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
pip3 install -r requirements.txt
cd ../checkout
pip3 install -r requirements.txt
cd ..
order-processor 和 checkout 服务使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 服务checkout 服务dapr run -f .
注意:由于 Windows 中未定义 Python3.exe,您可能需要在运行
dapr run -f .之前将dapr.yaml文件中的python3更改为python
预期输出
== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully
在本快速入门中运行 dapr run -f . 使用 dapr.yaml 多应用运行模板文件启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行多应用运行模板文件会启动项目中的所有应用程序。在本快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./order-processor/
appID: order-processor
appPort: 8001
command: ["python3", "app.py"]
- appID: checkout
appDirPath: ./checkout/
command: ["python3", "app.py"]
order-processor 服务order-processor 服务接收来自 checkout 服务的调用:
@app.route('/orders', methods=['POST'])
def getOrder():
data = request.json
print('Order received : ' + json.dumps(data), flush=True)
return json.dumps({'success': True}), 200, {
'ContentType': 'application/json'}
app.run(port=8001)
checkout 服务在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
headers = {'dapr-app-id': 'order-processor'}
result = requests.post(
url='%s/orders' % (base_url),
data=json.dumps(order),
headers=headers
)
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门克隆目录的根目录,导航到快速入门目录。
cd service_invocation/javascript/http
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
npm install
cd ../checkout
npm install
cd ..
order-processor 和 checkout 服务使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 服务checkout 服务dapr run -f .
预期输出
== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully
在本快速入门中运行 dapr run -f . 使用 dapr.yaml 多应用运行模板文件启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行多应用运行模板文件会启动项目中的所有应用程序。在本快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./order-processor/
appID: order-processor
appPort: 5001
command: ["npm", "start"]
- appID: checkout
appDirPath: ./checkout/
command: ["npm", "start"]
order-processor 服务order-processor 服务接收来自 checkout 服务的调用:
app.post('/orders', (req, res) => {
console.log("Order received:", req.body);
res.sendStatus(200);
});
checkout 服务在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
let axiosConfig = {
headers: {
"dapr-app-id": "order-processor"
}
};
const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig);
console.log("Order passed: " + res.config.data);
对于本示例,您需要:
注意:.NET 6 是本版本中 Dapr .NET SDK 包支持的最低 .NET 版本。从 Dapr v1.16 及更高版本开始,将仅支持 .NET 8 和 .NET 9。
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门克隆目录的根目录,导航到快速入门目录。
cd service_invocation/csharp/http
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..
order-processor 和 checkout 服务使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 服务checkout 服务dapr run -f .
预期输出
== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully
在本快速入门中运行 dapr run -f . 使用 dapr.yaml 多应用运行模板文件启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行多应用运行模板文件会启动项目中的所有应用程序。在本快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./order-processor/
appID: order-processor
appPort: 7001
command: ["dotnet", "run"]
- appID: checkout
appDirPath: ./checkout/
command: ["dotnet", "run"]
order-processor 服务order-processor 服务接收来自 checkout 服务的调用:
app.MapPost("/orders", (Order order) =>
{
Console.WriteLine("Order received : " + order);
return order.ToString();
});
checkout 服务在 checkout 服务的 Program.cs 文件中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
var client = DaprClient.CreateInvokeHttpClient(appId: "order-processor");
var cts = new CancellationTokenSource();
var response = await client.PostAsJsonAsync("/orders", order, cts.Token);
Console.WriteLine("Order passed: " + order);
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门克隆目录的根目录,导航到快速入门目录。
cd service_invocation/java/http
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
mvn clean install
cd ../checkout
mvn clean install
cd ..
order-processor 和 checkout 服务使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 服务checkout 服务dapr run -f .
预期输出
== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully
在本快速入门中运行 dapr run -f . 使用 dapr.yaml 多应用运行模板文件启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行多应用运行模板文件会启动项目中的所有应用程序。在本快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./order-processor/
appID: order-processor
appPort: 9001
command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"]
- appID: checkout
appDirPath: ./checkout/
command: ["java", "-jar", "target/CheckoutService-0.0.1-SNAPSHOT.jar"]
order-processor 服务order-processor 服务接收来自 checkout 服务的调用:
public String processOrders(@RequestBody Order body) {
System.out.println("Order received: "+ body.getOrderId());
return "CID" + body.getOrderId();
}
checkout 服务在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
.header("Content-Type", "application/json")
.header("dapr-app-id", "order-processor")
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Order passed: "+ orderId)
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门克隆目录的根目录,导航到快速入门目录。
cd service_invocation/go/http
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
go build .
cd ../checkout
go build .
cd ..
order-processor 和 checkout 服务使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 服务checkout 服务dapr run -f .
预期输出
== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully
在本快速入门中运行 dapr run -f . 使用 dapr.yaml 多应用运行模板文件启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行多应用运行模板文件会启动项目中的所有应用程序。在本快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./order-processor/
appID: order-processor
appPort: 6006
command: ["go", "run", "."]
- appID: checkout
appDirPath: ./checkout/
command: ["go", "run", "."]
order-processor 服务在 order-processor 服务中,每个订单通过 HTTP POST 请求接收,并由 getOrder 函数处理。
func getOrder(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
log.Printf("Order received : %s", string(data))
}
checkout 服务在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
req.Header.Add("dapr-app-id", "order-processor")
response, err := client.Do(req)
在继续快速入门之前,请选择您的首选语言。
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd service_invocation/python/http/order-processor
安装依赖项并构建应用程序:
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 8001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
@app.route('/orders', methods=['POST'])
def getOrder():
data = request.json
print('Order received : ' + json.dumps(data), flush=True)
return json.dumps({'success': True}), 200, {
'ContentType': 'application/json'}
app.run(port=8001)
checkout 服务在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd service_invocation/python/http/checkout
安装依赖项并构建应用程序:
pip3 install -r requirements.txt
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
headers = {'dapr-app-id': 'order-processor'}
result = requests.post(
url='%s/orders' % (base_url),
data=json.dumps(order),
headers=headers
)
您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processor 和 checkout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:
dapr run -f .
要停止所有应用程序,请运行:
dapr stop -f .
Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例会发现并相互通信。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd service_invocation/javascript/http/order-processor
安装依赖项:
npm install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 5001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- npm start
app.post('/orders', (req, res) => {
console.log("Order received:", req.body);
res.sendStatus(200);
});
checkout 服务在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd service_invocation/javascript/http/checkout
安装依赖项:
npm install
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- npm start
在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
let axiosConfig = {
headers: {
"dapr-app-id": "order-processor"
}
};
const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig);
console.log("Order passed: " + res.config.data);
您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processor 和 checkout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:
dapr run -f .
要停止所有应用程序,请运行:
dapr stop -f .
Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例会发现并相互通信。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd service_invocation/csharp/http/order-processor
安装依赖项:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 7001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- dotnet run
以下是订单处理器的 Program.cs 文件中的工作代码块。
app.MapPost("/orders", (Order order) =>
{
Console.WriteLine("Order received : " + order);
return order.ToString();
});
checkout 服务在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd service_invocation/csharp/http/checkout
安装依赖项:
dotnet restore
dotnet build
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- dotnet run
在 checkout 服务的 Program.cs 文件中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
var client = DaprClient.CreateInvokeHttpClient(appId: "order-processor");
var cts = new CancellationTokenSource();
var response = await client.PostAsJsonAsync("/orders", order, cts.Token);
Console.WriteLine("Order passed: " + order);
您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processor 和 checkout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:
dapr run -f .
要停止所有应用程序,请运行:
dapr stop -f .
Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例会发现并相互通信。
checkout 服务输出:
== APP == Order passed: Order { OrderId: 1 }
== APP == Order passed: Order { OrderId: 2 }
== APP == Order passed: Order { OrderId: 3 }
== APP == Order passed: Order { OrderId: 4 }
== APP == Order passed: Order { OrderId: 5 }
== APP == Order passed: Order { OrderId: 6 }
== APP == Order passed: Order { OrderId: 7 }
== APP == Order passed: Order { OrderId: 8 }
== APP == Order passed: Order { OrderId: 9 }
== APP == Order passed: Order { OrderId: 10 }
order-processor 服务输出:
== APP == Order received: Order { OrderId: 1 }
== APP == Order received: Order { OrderId: 2 }
== APP == Order received: Order { OrderId: 3 }
== APP == Order received: Order { OrderId: 4 }
== APP == Order received: Order { OrderId: 5 }
== APP == Order received: Order { OrderId: 6 }
== APP == Order received: Order { OrderId: 7 }
== APP == Order received: Order { OrderId: 8 }
== APP == Order received: Order { OrderId: 9 }
== APP == Order received: Order { OrderId: 10 }
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd service_invocation/java/http/order-processor
安装依赖项:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
public String processOrders(@RequestBody Order body) {
System.out.println("Order received: "+ body.getOrderId());
return "CID" + body.getOrderId();
}
checkout 服务在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd service_invocation/java/http/checkout
安装依赖项:
mvn clean install
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
.header("Content-Type", "application/json")
.header("dapr-app-id", "order-processor")
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Order passed: "+ orderId)
您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processor 和 checkout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:
dapr run -f .
要停止所有应用程序,请运行:
dapr stop -f .
Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例会发现并相互通信。
checkout 服务输出:
== APP == Order passed: 1
== APP == Order passed: 2
== APP == Order passed: 3
== APP == Order passed: 4
== APP == Order passed: 5
== APP == Order passed: 6
== APP == Order passed: 7
== APP == Order passed: 8
== APP == Order passed: 9
== APP == Order passed: 10
order-processor 服务输出:
== APP == Order received: 1
== APP == Order received: 2
== APP == Order received: 3
== APP == Order received: 4
== APP == Order received: 5
== APP == Order received: 6
== APP == Order received: 7
== APP == Order received: 8
== APP == Order received: 9
== APP == Order received: 10
对于本示例,您需要:
克隆快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd service_invocation/go/http/order-processor
安装依赖项:
go build .
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 6006 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- go run .
每个订单通过 HTTP POST 请求接收,并由 getOrder 函数处理。
func getOrder(w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
log.Printf("Order received : %s", string(data))
}
checkout 服务在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd service_invocation/go/http/checkout
安装依赖项:
go build .
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- go run .
在 checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头即可启用服务调用,该头指定目标服务的 ID。
req.Header.Add("dapr-app-id", "order-processor")
response, err := client.Do(req)
您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processor 和 checkout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:
dapr run -f .
要停止所有应用程序,请运行:
dapr stop -f .
Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例会发现并相互通信。
checkout 服务输出:
== APP == Order passed: {"orderId":1}
== APP == Order passed: {"orderId":2}
== APP == Order passed: {"orderId":3}
== APP == Order passed: {"orderId":4}
== APP == Order passed: {"orderId":5}
== APP == Order passed: {"orderId":6}
== APP == Order passed: {"orderId":7}
== APP == Order passed: {"orderId":8}
== APP == Order passed: {"orderId":9}
== APP == Order passed: {"orderId":10}
order-processor 服务输出:
== APP == Order received : {"orderId":1}
== APP == Order received : {"orderId":2}
== APP == Order received : {"orderId":3}
== APP == Order received : {"orderId":4}
== APP == Order received : {"orderId":5}
== APP == Order received : {"orderId":6}
== APP == Order received : {"orderId":7}
== APP == Order received : {"orderId":8}
== APP == Order received : {"orderId":9}
== APP == Order received : {"orderId":10}
我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?
请加入我们的 discord 频道参与讨论。
让我们来了解一下 Dapr 的[发布订阅构建块](https://docs.dapr.io/zh-hans/developing-applications/building-blocks/pubsub/)。在本快速入门中,您将运行一个发布者微服务和一个订阅者微服务,以演示 Dapr 如何实现发布订阅模式。

您可以通过以下方式尝试此发布订阅快速入门:
在继续快速入门之前,请选择您偏好的语言特定的 Dapr SDK。
对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门目录的根目录导航到发布/订阅目录:
cd pub_sub/python/sdk
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./checkout
pip3 install -r requirements.txt
cd ..
cd ./order-processor
pip3 install -r requirements.txt
cd ..
cd ./order-processor-fastapi
pip3 install -r requirements.txt
cd ..
使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 订阅者checkout 发布者dapr run -f .
注意:由于 Windows 中未定义 Python3.exe,您可能需要在运行
dapr run -f .之前将 [dapr.yaml](#dapryaml-multi-app-run-template-file)文件中的python3更改为python
预期输出
== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully
当您在 Dapr 安装期间运行 dapr init 时,在 .dapr/components 目录中生成了以下 YAML 文件:
在此快速入门中运行 dapr run -f . 启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行[多应用运行模板文件](https://docs.dapr.io/zh-hans/developing-applications/local-development/multi-app-dapr-run/)会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: order-processor-sdk
appDirPath: ./order-processor/
appPort: 6001
command: ["uvicorn", "app:app"]
- appID: checkout-sdk
appDirPath: ./checkout/
command: ["python3", "app.py"]
pubsub.yaml 组件文件使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在组件 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。order-processor 订阅者在 order-processor 订阅者中,您订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得您的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
# Register Dapr pub/sub subscriptions
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
subscriptions = [{
'pubsubname': 'orderpubsub',
'topic': 'orders',
'route': 'orders'
}]
print('Dapr pub/sub is subscribed to: ' + json.dumps(subscriptions))
return jsonify(subscriptions)
# Dapr subscription in /dapr/subscribe sets up this route
@app.route('/orders', methods=['POST'])
def orders_subscriber():
event = from_http(request.headers, request.get_data())
print('Subscriber received : ' + event.data['orderid'], flush=True)
return json.dumps({'success': True}), 200, {
'ContentType': 'application/json'}
app.run(port=5001)
checkout 发布者在 checkout 发布者中,您向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
with DaprClient() as client:
# Publish an event/message using Dapr PubSub
result = client.publish_event(
pubsub_name='orderpubsub',
topic_name='orders',
data=json.dumps(order),
data_content_type='application/json',
)
对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门目录的根目录导航到发布/订阅目录:
cd pub_sub/javascript/sdk
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
npm install
cd ..
cd ./checkout
npm install
cd ..
使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 订阅者checkout 发布者dapr run -f .
预期输出
== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully
当您在 Dapr 安装期间运行 dapr init 时,在 .dapr/components 目录中生成了以下 YAML 文件:
在此快速入门中运行 dapr run -f . 启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行[多应用运行模板文件](https://docs.dapr.io/zh-hans/developing-applications/local-development/multi-app-dapr-run/)会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: order-processor
appDirPath: ./order-processor/
appPort: 5002
command: ["npm", "run", "start"]
- appID: checkout-sdk
appDirPath: ./checkout/
command: ["npm", "run", "start"]
pubsub.yaml 组件文件使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在组件 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。order-processor 订阅者在 order-processor 订阅者中,您订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得您的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));
checkout 发布者在 checkout 发布者服务中,您向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
const client = new DaprClient();
await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
console.log("Published data: " + JSON.stringify(order));
对于此示例,您需要:
注意:.NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。Dapr v1.16 及更高版本将仅支持 .NET 8 和 .NET 9。
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门目录的根目录导航到发布/订阅目录:
cd pub_sub/csharp/sdk
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..
使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 订阅者checkout 发布者dapr run -f .
预期输出
== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully
当您在 Dapr 安装期间运行 dapr init 时,在 .dapr/components 目录中生成了以下 YAML 文件:
在此快速入门中运行 dapr run -f . 启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行[多应用运行模板文件](https://docs.dapr.io/zh-hans/developing-applications/local-development/multi-app-dapr-run/)会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: order-processor
appDirPath: ./order-processor/
appPort: 7006
command: ["dotnet", "run"]
- appID: checkout-sdk
appDirPath: ./checkout/
command: ["dotnet", "run"]
pubsub.yaml 组件文件使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在组件 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。order-processor 订阅者在 order-processor 订阅者中,您订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得您的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
// Dapr subscription in [Topic] routes orders topic to this route
app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => {
Console.WriteLine("Subscriber received : " + order);
return Results.Ok(order);
});
public record Order([property: JsonPropertyName("orderId")] int OrderId);
checkout 发布者在 checkout 发布者中,您向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
using var client = new DaprClientBuilder().Build();
await client.PublishEventAsync("orderpubsub", "orders", order);
Console.WriteLine("Published data: " + order);
对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门目录的根目录导航到发布/订阅目录:
cd pub_sub/java/sdk
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
mvn clean install
cd ..
cd ./checkout
mvn clean install
cd ..
使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 订阅者checkout 发布者dapr run -f .
预期输出
== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully
当您在 Dapr 安装期间运行 dapr init 时,在 .dapr/components 目录中生成了以下 YAML 文件:
在此快速入门中运行 dapr run -f . 启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行[多应用运行模板文件](https://docs.dapr.io/zh-hans/developing-applications/local-development/multi-app-dapr-run/)会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: order-processor-sdk
appDirPath: ./order-processor/target/
appPort: 8080
command: ["java", "-jar", "OrderProcessingService-0.0.1-SNAPSHOT.jar"]
- appID: checkout-sdk
appDirPath: ./checkout/target/
command: ["java", "-jar", "CheckoutService-0.0.1-SNAPSHOT.jar"]
pubsub.yaml 组件文件使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在组件 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。order-processor 订阅者在 order-processor 订阅者中,您订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得您的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
@Topic(name = "orders", pubsubName = "orderpubsub")
@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE)
public Mono<ResponseEntity> getCheckout(@RequestBody(required = false) CloudEvent<Order> cloudEvent) {
return Mono.fromSupplier(() -> {
try {
logger.info("Subscriber received: " + cloudEvent.getData().getOrderId());
return ResponseEntity.ok("SUCCESS");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
checkout 发布者在 checkout 发布者中,您向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
DaprClient client = new DaprClientBuilder().build();
client.publishEvent(
PUBSUB_NAME,
TOPIC_NAME,
order).block();
logger.info("Published data: " + order.getOrderId());
对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
从快速入门目录的根目录导航到发布/订阅目录:
cd pub_sub/go/sdk
为 order-processor 和 checkout 应用程序安装依赖项:
cd ./order-processor
go build .
cd ../checkout
go build .
cd ..
使用以下命令,同时运行以下服务及其各自的 Dapr 边车:
order-processor 订阅者checkout 发布者dapr run -f .
预期输出
== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully
当您在 Dapr 安装期间运行 dapr init 时,在 .dapr/components 目录中生成了以下 YAML 文件:
在此快速入门中运行 dapr run -f . 启动了订阅者和发布者应用程序。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行[多应用运行模板文件](https://docs.dapr.io/zh-hans/developing-applications/local-development/multi-app-dapr-run/)会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: order-processor
appDirPath: ./order-processor/
appPort: 6005
command: ["go", "run", "."]
- appID: checkout-sdk
appDirPath: ./checkout/
command: ["go", "run", "."]
pubsub.yaml 组件文件使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在组件 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。order-processor 订阅者在 order-processor 订阅者中,您订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得您的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
fmt.Println("Subscriber received: ", e.Data)
return false, nil
}
checkout 发布者在 checkout 发布者中,您向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
client, err := dapr.NewClient()
if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil {
panic(err)
}
fmt.Println("Published data: ", order)
在继续快速入门之前,请选择您偏好的语言特定的 Dapr SDK。
对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd pub_sub/python/sdk/order-processor
安装依赖项:
pip3 install -r requirements.txt
运行 order-processor 订阅者服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6002 -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
在 order-processor 订阅者中,我们订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得我们的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
# Register Dapr pub/sub subscriptions
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
subscriptions = [{
'pubsubname': 'orderpubsub',
'topic': 'orders',
'route': 'orders'
}]
print('Dapr pub/sub is subscribed to: ' + json.dumps(subscriptions))
return jsonify(subscriptions)
# Dapr subscription in /dapr/subscribe sets up this route
@app.route('/orders', methods=['POST'])
def orders_subscriber():
event = from_http(request.headers, request.get_data())
print('Subscriber received : ' + event.data['orderid'], flush=True)
return json.dumps({'success': True}), 200, {
'ContentType': 'application/json'}
app.run(port=5001)
在新的终端窗口中,导航到 checkout 目录。
cd pub_sub/python/sdk/checkout
安装依赖项:
pip3 install -r requirements.txt
运行 checkout 发布者服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../components/ -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
在 checkout 发布者中,我们向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
with DaprClient() as client:
# Publish an event/message using Dapr PubSub
result = client.publish_event(
pubsub_name='orderpubsub',
topic_name='orders',
data=json.dumps(order),
data_content_type='application/json',
)
发布者向 Dapr 边车发送订单,而订阅者接收它们。
发布者输出:
== APP == INFO:root:Published data: {"orderId": 1}
== APP == INFO:root:Published data: {"orderId": 2}
== APP == INFO:root:Published data: {"orderId": 3}
== APP == INFO:root:Published data: {"orderId": 4}
== APP == INFO:root:Published data: {"orderId": 5}
== APP == INFO:root:Published data: {"orderId": 6}
== APP == INFO:root:Published data: {"orderId": 7}
== APP == INFO:root:Published data: {"orderId": 8}
== APP == INFO:root:Published data: {"orderId": 9}
== APP == INFO:root:Published data: {"orderId": 10}
订阅者输出:
== APP == INFO:root:Subscriber received: {"orderId": 1}
== APP == INFO:root:Subscriber received: {"orderId": 2}
== APP == INFO:root:Subscriber received: {"orderId": 3}
== APP == INFO:root:Subscriber received: {"orderId": 4}
== APP == INFO:root:Subscriber received: {"orderId": 5}
== APP == INFO:root:Subscriber received: {"orderId": 6}
== APP == INFO:root:Subscriber received: {"orderId": 7}
== APP == INFO:root:Subscriber received: {"orderId": 8}
== APP == INFO:root:Subscriber received: {"orderId": 9}
== APP == INFO:root:Subscriber received: {"orderId": 10}
pubsub.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地计算机上运行 Redis 容器,位于:
%UserProfile%\.dapr\components\pubsub.yaml 下~/.dapr/components/pubsub.yaml 下使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd pub_sub/javascript/sdk/order-processor
安装依赖项,这将包括来自 JavaScript SDK 的 @dapr/dapr 包:
npm install
验证服务目录中包含以下文件:
package.jsonpackage-lock.json运行 order-processor 订阅者服务以及 Dapr 边车。
dapr run --app-port 5002 --app-id order-processing --app-protocol http --dapr-http-port 3501 --resources-path ../../../components -- npm run start
在 order-processor 订阅者中,我们订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得我们的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));
在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd pub_sub/javascript/sdk/checkout
安装依赖项,这将包括来自 JavaScript SDK 的 @dapr/dapr 包:
npm install
验证服务目录中包含以下文件:
package.jsonpackage-lock.json运行 checkout 发布者服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --resources-path ../../../components -- npm run start
在 checkout 发布者服务中,我们向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
const client = new DaprClient();
await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
console.log("Published data: " + JSON.stringify(order));
请注意,如上面的代码中所指定,发布者向 Dapr 边车推送一个随机数,而订阅者接收它。
发布者输出:
== APP == Published data: {"orderId":1}
== APP == Published data: {"orderId":2}
== APP == Published data: {"orderId":3}
== APP == Published data: {"orderId":4}
== APP == Published data: {"orderId":5}
== APP == Published data: {"orderId":6}
== APP == Published data: {"orderId":7}
== APP == Published data: {"orderId":8}
== APP == Published data: {"orderId":9}
== APP == Published data: {"orderId":10}
订阅者输出:
== APP == Subscriber received: {"orderId":1}
== APP == Subscriber received: {"orderId":2}
== APP == Subscriber received: {"orderId":3}
== APP == Subscriber received: {"orderId":4}
== APP == Subscriber received: {"orderId":5}
== APP == Subscriber received: {"orderId":6}
== APP == Subscriber received: {"orderId":7}
== APP == Subscriber received: {"orderId":8}
== APP == Subscriber received: {"orderId":9}
== APP == Subscriber received: {"orderId":10}
pubsub.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地计算机上运行 Redis 容器,位于:
%UserProfile%\.dapr\components\pubsub.yaml 下~/.dapr/components/pubsub.yaml 下使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd pub_sub/csharp/sdk/order-processor
恢复 NuGet 包:
dotnet restore
dotnet build
运行 order-processor 订阅者服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components --app-port 7006 -- dotnet run
在 order-processor 订阅者中,我们订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得我们的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
// Dapr subscription in [Topic] routes orders topic to this route
app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => {
Console.WriteLine("Subscriber received : " + order);
return Results.Ok(order);
});
public record Order([property: JsonPropertyName("orderId")] int OrderId);
在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd pub_sub/csharp/sdk/checkout
恢复 NuGet 包:
dotnet restore
dotnet build
运行 checkout 发布者服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../components -- dotnet run
在 checkout 发布者中,我们向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
using var client = new DaprClientBuilder().Build();
await client.PublishEventAsync("orderpubsub", "orders", order);
Console.WriteLine("Published data: " + order);
请注意,如上面的代码中所指定,发布者向 Dapr 边车推送一个随机数,而订阅者接收它。
发布者输出:
== APP == Published data: Order { OrderId = 1 }
== APP == Published data: Order { OrderId = 2 }
== APP == Published data: Order { OrderId = 3 }
== APP == Published data: Order { OrderId = 4 }
== APP == Published data: Order { OrderId = 5 }
== APP == Published data: Order { OrderId = 6 }
== APP == Published data: Order { OrderId = 7 }
== APP == Published data: Order { OrderId = 8 }
== APP == Published data: Order { OrderId = 9 }
== APP == Published data: Order { OrderId = 10 }
订阅者输出:
== APP == Subscriber received: Order { OrderId = 1 }
== APP == Subscriber received: Order { OrderId = 2 }
== APP == Subscriber received: Order { OrderId = 3 }
== APP == Subscriber received: Order { OrderId = 4 }
== APP == Subscriber received: Order { OrderId = 5 }
== APP == Subscriber received: Order { OrderId = 6 }
== APP == Subscriber received: Order { OrderId = 7 }
== APP == Subscriber received: Order { OrderId = 8 }
== APP == Subscriber received: Order { OrderId = 9 }
== APP == Subscriber received: Order { OrderId = 10 }
pubsub.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地计算机上运行 Redis 容器,位于:
%UserProfile%\.dapr\components\pubsub.yaml 下~/.dapr/components/pubsub.yaml 下使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd pub_sub/java/sdk/order-processor
安装依赖项:
mvn clean install
运行 order-processor 订阅者服务以及 Dapr 边车。
dapr run --app-port 8080 --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
在 order-processor 订阅者中,我们订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得我们的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
@Topic(name = "orders", pubsubName = "orderpubsub")
@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE)
public Mono<ResponseEntity> getCheckout(@RequestBody(required = false) CloudEvent<Order> cloudEvent) {
return Mono.fromSupplier(() -> {
try {
logger.info("Subscriber received: " + cloudEvent.getData().getOrderId());
return ResponseEntity.ok("SUCCESS");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd pub_sub/java/sdk/checkout
安装依赖项:
mvn clean install
运行 checkout 发布者服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../components -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
在 checkout 发布者中,我们向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
DaprClient client = new DaprClientBuilder().build();
client.publishEvent(
PUBSUB_NAME,
TOPIC_NAME,
order).block();
logger.info("Published data: " + order.getOrderId());
请注意,如上面的代码中所指定,发布者向 Dapr 边车推送一个随机数,而订阅者接收它。
发布者输出:
== APP == 7194 [main] INFO com.service.CheckoutServiceApplication - Published data: 1
== APP == 12213 [main] INFO com.service.CheckoutServiceApplication - Published data: 2
== APP == 17233 [main] INFO com.service.CheckoutServiceApplication - Published data: 3
== APP == 22252 [main] INFO com.service.CheckoutServiceApplication - Published data: 4
== APP == 27276 [main] INFO com.service.CheckoutServiceApplication - Published data: 5
== APP == 32320 [main] INFO com.service.CheckoutServiceApplication - Published data: 6
== APP == 37340 [main] INFO com.service.CheckoutServiceApplication - Published data: 7
== APP == 42356 [main] INFO com.service.CheckoutServiceApplication - Published data: 8
== APP == 47386 [main] INFO com.service.CheckoutServiceApplication - Published data: 9
== APP == 52410 [main] INFO com.service.CheckoutServiceApplication - Published data: 10
订阅者输出:
== APP == 2022-03-07 13:31:19.551 INFO 43512 --- [nio-8080-exec-5] c.s.c.OrderProcessingServiceController : Subscriber received: 1
== APP == 2022-03-07 13:31:19.552 INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController : Subscriber received: 2
== APP == 2022-03-07 13:31:19.551 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 3
== APP == 2022-03-07 13:31:19.552 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 4
== APP == 2022-03-07 13:31:19.553 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 5
== APP == 2022-03-07 13:31:19.553 INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController : Subscriber received: 6
== APP == 2022-03-07 13:31:22.849 INFO 43512 --- [nio-8080-exec-3] c.s.c.OrderProcessingServiceController : Subscriber received: 7
== APP == 2022-03-07 13:31:27.866 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 8
== APP == 2022-03-07 13:31:32.895 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 9
== APP == 2022-03-07 13:31:37.919 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 10
pubsub.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地计算机上运行 Redis 容器,位于:
%UserProfile%\.dapr\components\pubsub.yaml 下~/.dapr/components/pubsub.yaml 下使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
scopes:
- orderprocessing
- checkout
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。对于此示例,您需要:
克隆 快速入门存储库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。
cd pub_sub/go/sdk/order-processor
安装依赖项并构建应用程序:
go build .
运行 order-processor 订阅者服务以及 Dapr 边车。
dapr run --app-port 6005 --app-id order-processor-sdk --app-protocol http --dapr-http-port 3501 --resources-path ../../../components -- go run .
在 order-processor 订阅者中,我们订阅名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders。这使得我们的应用程序代码可以通过 Dapr 边车与 Redis 组件实例通信。
func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
fmt.Println("Subscriber received: ", e.Data)
return false, nil
}
在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。
cd pub_sub/go/sdk/checkout
安装依赖项并构建应用程序:
go build .
运行 checkout 发布者服务以及 Dapr 边车。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --resources-path ../../../components -- go run .
在 checkout 发布者中,我们向名为 orderpubsub 的 Redis 实例 (在 pubsub.yaml 组件中定义)和主题 orders发布 orderId 消息。服务启动后,它会循环发布:
client, err := dapr.NewClient()
if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil {
panic(err)
}
fmt.Println("Published data: ", order)
请注意,如上面的代码中所指定,发布者向 Dapr 边车推送一个编号的消息,而订阅者接收它。
发布者输出:
== APP == dapr client initializing for: 127.0.0.1:63293
== APP == Published data: {"orderId":1}
== APP == Published data: {"orderId":2}
== APP == Published data: {"orderId":3}
== APP == Published data: {"orderId":4}
== APP == Published data: {"orderId":5}
== APP == Published data: {"orderId":6}
== APP == Published data: {"orderId":7}
== APP == Published data: {"orderId":8}
== APP == Published data: {"orderId":9}
== APP == Published data: {"orderId":10}
订阅者输出:
== APP == Subscriber received: {"orderId":1}
== APP == Subscriber received: {"orderId":2}
== APP == Subscriber received: {"orderId":3}
== APP == Subscriber received: {"orderId":4}
== APP == Subscriber received: {"orderId":5}
== APP == Subscriber received: {"orderId":6}
== APP == Subscriber received: {"orderId":7}
== APP == Subscriber received: {"orderId":8}
== APP == Subscriber received: {"orderId":9}
== APP == Subscriber received: {"orderId":10}
注意:接收它们的顺序可能会有所不同。
pubsub.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地计算机上运行 Redis 容器,位于:
%UserProfile%\.dapr\components\pubsub.yaml 下~/.dapr/components/pubsub.yaml 下使用 pubsub.yaml 组件,您可以在不更改应用程序代码的情况下轻松更换底层组件。
为此快速入门包含的 Redis pubsub.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
scopes:
- orderprocessing
- checkout
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式。spec/metadata 定义与组件实例的连接。scopes 指定哪些应用程序可以使用该组件。我们正在不断努力改进我们的快速入门示例,重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?
加入我们的 discord 频道 进行讨论。
让我们来了解一下 Dapr 工作流构建块。在本快速入门中,你将创建一个简单的控制台应用程序,演示 Dapr 的工作流编程模型和工作流管理 API。
在本指南中,你将:
order-processor 应用程序。
该工作流包含以下活动:
NotifyActivity:使用记录器在工作流过程中输出消息。VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。RequestApprovalActivity:请求对超过特定成本阈值的订单进行批准。ProcessPaymentActivity:处理并授权付款。UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。该工作流还包含业务逻辑:
RequestApprovalActivity 并等待外部批准事件。
在继续快速入门之前,请选择你首选的语言特定 Dapr SDK。
order-processor 控制台应用程序启动并管理 order_processing_workflow,它模拟从商店购买商品。该工作流由五个独特的工作流活动或任务组成:
notify_activity:使用记录器在工作流过程中输出消息。这些消息会在以下情况下通知你:verify_inventory_activity:检查状态存储以确保有足够的库存可供购买。request_approval_activity:请求对超过特定成本阈值的订单进行批准。process_payment_activity:处理并授权付款。update_inventory_activity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。对于此示例,你需要:
克隆 快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在新的终端窗口中,导航到 order-processor 目录:
cd workflows/python/sdk/order-processor
安装 Dapr Python SDK 包:
pip3 install -r requirements.txt
返回到 python/sdk 目录:
cd ..
在终端中,使用 多应用运行 启动订单处理应用程序和 Dapr 边车。从 python/sdk 目录,运行以下命令:
dapr run -f .
这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。
预期输出:
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == 2025-02-13 11:44:11.357 durabletask-worker INFO: Starting gRPC worker that connects to dns:127.0.0.1:38891
== APP - order-processor == 2025-02-13 11:44:11.361 durabletask-worker INFO: Successfully connected to dns:127.0.0.1:38891. Waiting for work items...
== APP - order-processor == INFO:NotifyActivity:Received order 6830cb00174544a0b062ba818e14fddc for 1 cars at $5000 !
== APP - order-processor == 2025-02-13 11:44:14.157 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:VerifyInventoryActivity:Verifying inventory for order 6830cb00174544a0b062ba818e14fddc of 1 cars
== APP - order-processor == INFO:VerifyInventoryActivity:There are 10 Cars available for purchase
== APP - order-processor == 2025-02-13 11:44:14.171 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:ProcessPaymentActivity:Processing payment: 6830cb00174544a0b062ba818e14fddc for 1 cars at 5000 USD
== APP - order-processor == INFO:ProcessPaymentActivity:Payment for request ID 6830cb00174544a0b062ba818e14fddc processed successfully
== APP - order-processor == 2025-02-13 11:44:14.177 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:UpdateInventoryActivity:Checking inventory for order 6830cb00174544a0b062ba818e14fddc for 1 cars
== APP - order-processor == INFO:UpdateInventoryActivity:There are now 9 cars left in stock
== APP - order-processor == 2025-02-13 11:44:14.189 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Order 6830cb00174544a0b062ba818e14fddc has completed!
== APP - order-processor == 2025-02-13 11:44:14.195 durabletask-worker INFO: 6830cb00174544a0b062ba818e14fddc: Orchestration completed with status: COMPLETED
== APP - order-processor == item: InventoryItem(item_name=Paperclip, per_item_cost=5, quantity=100)
== APP - order-processor == item: InventoryItem(item_name=Cars, per_item_cost=5000, quantity=10)
== APP - order-processor == item: InventoryItem(item_name=Computers, per_item_cost=500, quantity=100)
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == Starting order workflow, purchasing 1 of cars
== APP - order-processor == 2025-02-13 11:44:16.363 durabletask-client INFO: Starting new 'order_processing_workflow' instance with ID = 'fc8a507e4a2246d2917d3ad4e3111240'.
== APP - order-processor == 2025-02-13 11:44:16.366 durabletask-client INFO: Waiting 30s for instance 'fc8a507e4a2246d2917d3ad4e3111240' to complete.
== APP - order-processor == 2025-02-13 11:44:16.366 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Received order fc8a507e4a2246d2917d3ad4e3111240 for 1 cars at $5000 !
== APP - order-processor == 2025-02-13 11:44:16.373 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:VerifyInventoryActivity:Verifying inventory for order fc8a507e4a2246d2917d3ad4e3111240 of 1 cars
== APP - order-processor == INFO:VerifyInventoryActivity:There are 10 Cars available for purchase
== APP - order-processor == 2025-02-13 11:44:16.383 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:ProcessPaymentActivity:Processing payment: fc8a507e4a2246d2917d3ad4e3111240 for 1 cars at 5000 USD
== APP - order-processor == INFO:ProcessPaymentActivity:Payment for request ID fc8a507e4a2246d2917d3ad4e3111240 processed successfully
== APP - order-processor == 2025-02-13 11:44:16.390 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:UpdateInventoryActivity:Checking inventory for order fc8a507e4a2246d2917d3ad4e3111240 for 1 cars
== APP - order-processor == INFO:UpdateInventoryActivity:There are now 9 cars left in stock
== APP - order-processor == 2025-02-13 11:44:16.403 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestrator yielded with 1 task(s) and 0 event(s) outstanding.
== APP - order-processor == INFO:NotifyActivity:Order fc8a507e4a2246d2917d3ad4e3111240 has completed!
== APP - order-processor == 2025-02-13 11:44:16.411 durabletask-worker INFO: fc8a507e4a2246d2917d3ad4e3111240: Orchestration completed with status: COMPLETED
== APP - order-processor == 2025-02-13 11:44:16.425 durabletask-client INFO: Instance 'fc8a507e4a2246d2917d3ad4e3111240' completed.
== APP - order-processor == 2025-02-13 11:44:16.425 durabletask-worker INFO: Stopping gRPC worker...
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: Disconnected from dns:127.0.0.1:38891
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: No longer listening for work items
== APP - order-processor == 2025-02-13 11:44:16.426 durabletask-worker INFO: Worker shutdown completed
== APP - order-processor == Workflow completed! Result: {"processed": true, "__durabletask_autoobject__": true}
运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:
docker run -d -p 9411:9411 openzipkin/zipkin
在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

当你运行 dapr run -f . 时:
fc8a507e4a2246d2917d3ad4e3111240)并调度了工作流。notify_activity 工作流活动发送一条通知,说明已收到一辆汽车的订单。verify_inventory_activity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。request_approval_activity 活动。process_payment_activity 工作流活动开始处理订单 fc8a507e4a2246d2917d3ad4e3111240 的付款并确认是否成功。update_inventory_activity 工作流活动在订单处理后使用当前可用的汽车更新库存。notify_activity 工作流活动发送一条通知,说明订单 fc8a507e4a2246d2917d3ad4e3111240 已完成。order-processor/app.py在应用程序的程序文件中:
from datetime import datetime
from time import sleep
from dapr.clients import DaprClient
from dapr.conf import settings
from dapr.ext.workflow import DaprWorkflowClient, WorkflowStatus
from workflow import wfr, order_processing_workflow
from model import InventoryItem, OrderPayload
store_name = "statestore"
workflow_name = "order_processing_workflow"
default_item_name = "cars"
class WorkflowConsoleApp:
def main(self):
print("*** Welcome to the Dapr Workflow console app sample!", flush=True)
print("*** Using this app, you can place orders that start workflows.", flush=True)
wfr.start()
# Wait for the sidecar to become available
sleep(5)
wfClient = DaprWorkflowClient()
baseInventory = {
"paperclip": InventoryItem("Paperclip", 5, 100),
"cars": InventoryItem("Cars", 5000, 10),
"computers": InventoryItem("Computers", 500, 100),
}
daprClient = DaprClient(address=f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}')
self.restock_inventory(daprClient, baseInventory)
print("==========Begin the purchase of item:==========", flush=True)
item_name = default_item_name
order_quantity = 1
total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost
order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost)
print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True)
instance_id = wfClient.schedule_new_workflow(
workflow=order_processing_workflow, input=order.to_json())
try:
state = wfClient.wait_for_workflow_completion(instance_id=instance_id, timeout_in_seconds=30)
if not state:
print("Workflow not found!")
elif state.runtime_status.name == 'COMPLETED':
print(f'Workflow completed! Result: {state.serialized_output}')
else:
print(f'Workflow failed! Status: {state.runtime_status.name}') # not expected
except TimeoutError:
print('*** Workflow timed out!')
wfr.shutdown()
def restock_inventory(self, daprClient: DaprClient, baseInventory):
for key, item in baseInventory.items():
print(f'item: {item}')
item_str = f'{{"name": "{item.item_name}", "quantity": {item.quantity},\
"per_item_cost": {item.per_item_cost}}}'
daprClient.save_state(store_name, key, item_str)
if __name__ == '__main__':
app = WorkflowConsoleApp()
app.main()
order-processor/workflow.py在 workflow.py 中,工作流被定义为一个类,其中包含所有相关任务(由工作流活动确定)。
from datetime import timedelta
import logging
import json
from dapr.ext.workflow import DaprWorkflowContext, WorkflowActivityContext, WorkflowRuntime, when_any
from dapr.clients import DaprClient
from dapr.conf import settings
from model import InventoryItem, Notification, InventoryRequest, OrderPayload, OrderResult,\
PaymentRequest, InventoryResult
store_name = "statestore"
wfr = WorkflowRuntime()
logging.basicConfig(level=logging.INFO)
@wfr.workflow(name="order_processing_workflow")
def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: str):
"""Defines the order processing workflow.
When the order is received, the inventory is checked to see if there is enough inventory to
fulfill the order. If there is enough inventory, the payment is processed and the inventory is
updated. If there is not enough inventory, the order is rejected.
If the total order is greater than $5,000, the order is sent to a manager for approval.
"""
order_id = ctx.instance_id
order_payload=json.loads(order_payload_str)
yield ctx.call_activity(notify_activity,
input=Notification(message=('Received order ' +order_id+ ' for '
+f'{order_payload["quantity"]}' +' ' +f'{order_payload["item_name"]}'
+' at $'+f'{order_payload["total_cost"]}' +' !')))
result = yield ctx.call_activity(verify_inventory_activity,
input=InventoryRequest(request_id=order_id,
item_name=order_payload["item_name"],
quantity=order_payload["quantity"]))
if not result.success:
yield ctx.call_activity(notify_activity,
input=Notification(message='Insufficient inventory for '
+f'{order_payload["item_name"]}'+'!'))
return OrderResult(processed=False)
if order_payload["total_cost"] > 5000:
yield ctx.call_activity(request_approval_activity, input=order_payload)
approval_task = ctx.wait_for_external_event("approval_event")
timeout_event = ctx.create_timer(timedelta(seconds=30))
winner = yield when_any([approval_task, timeout_event])
if winner == timeout_event:
yield ctx.call_activity(notify_activity,
input=Notification(message='Order '+order_id
+' has been cancelled due to approval timeout.'))
return OrderResult(processed=False)
approval_result = yield approval_task
if approval_result == False:
yield ctx.call_activity(notify_activity, input=Notification(
message=f'Order {order_id} was not approved'))
return OrderResult(processed=False)
yield ctx.call_activity(process_payment_activity, input=PaymentRequest(
request_id=order_id, item_being_purchased=order_payload["item_name"],
amount=order_payload["total_cost"], quantity=order_payload["quantity"]))
try:
yield ctx.call_activity(update_inventory_activity,
input=PaymentRequest(request_id=order_id,
item_being_purchased=order_payload["item_name"],
amount=order_payload["total_cost"],
quantity=order_payload["quantity"]))
except Exception:
yield ctx.call_activity(notify_activity,
input=Notification(message=f'Order {order_id} Failed!'))
return OrderResult(processed=False)
yield ctx.call_activity(notify_activity, input=Notification(
message=f'Order {order_id} has completed!'))
return OrderResult(processed=True)
@wfr.activity(name="notify_activity")
def notify_activity(ctx: WorkflowActivityContext, input: Notification):
"""Defines Notify Activity. This is used by the workflow to send out a notification"""
# Create a logger
logger = logging.getLogger('NotifyActivity')
logger.info(input.message)
@wfr.activity(name="process_payment_activity")
def process_payment_activity(ctx: WorkflowActivityContext, input: PaymentRequest):
"""Defines Process Payment Activity.This is used by the workflow to process a payment"""
logger = logging.getLogger('ProcessPaymentActivity')
logger.info('Processing payment: '+f'{input.request_id}'+' for '
+f'{input.quantity}' +' ' +f'{input.item_being_purchased}'+' at '+f'{input.amount}'
+' USD')
logger.info(f'Payment for request ID {input.request_id} processed successfully')
@wfr.activity(name="verify_inventory_activity")
def verify_inventory_activity(ctx: WorkflowActivityContext,
input: InventoryRequest) -> InventoryResult:
"""Defines Verify Inventory Activity. This is used by the workflow to verify if inventory
is available for the order"""
logger = logging.getLogger('VerifyInventoryActivity')
logger.info('Verifying inventory for order '+f'{input.request_id}'+' of '
+f'{input.quantity}' +' ' +f'{input.item_name}')
with DaprClient(f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}') as client:
result = client.get_state(store_name, input.item_name)
if result.data is None:
return InventoryResult(False, None)
res_json=json.loads(str(result.data.decode('utf-8')))
logger.info(f'There are {res_json["quantity"]} {res_json["name"]} available for purchase')
inventory_item = InventoryItem(item_name=input.item_name,
per_item_cost=res_json['per_item_cost'],
quantity=res_json['quantity'])
if res_json['quantity'] >= input.quantity:
return InventoryResult(True, inventory_item)
return InventoryResult(False, None)
@wfr.activity(name="update_inventory_activity")
def update_inventory_activity(ctx: WorkflowActivityContext,
input: PaymentRequest) -> InventoryResult:
"""Defines Update Inventory Activity. This is used by the workflow to check if inventory
is sufficient to fulfill the order and updates inventory by reducing order quantity from
inventory."""
logger = logging.getLogger('UpdateInventoryActivity')
logger.info('Checking inventory for order ' +f'{input.request_id}'+' for '
+f'{input.quantity}' +' ' +f'{input.item_being_purchased}')
with DaprClient(f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}') as client:
result = client.get_state(store_name, input.item_being_purchased)
res_json=json.loads(str(result.data.decode('utf-8')))
new_quantity = res_json['quantity'] - input.quantity
per_item_cost = res_json['per_item_cost']
if new_quantity < 0:
raise ValueError('Inventory update for request ID '+f'{input.item_being_purchased}'
+' could not be processed. Insufficient inventory.')
new_val = f'{{"name": "{input.item_being_purchased}", "quantity": {str(new_quantity)}, "per_item_cost": {str(per_item_cost)}}}'
client.save_state(store_name, input.item_being_purchased, new_val)
logger.info(f'There are now {new_quantity} {input.item_being_purchased} left in stock')
@wfr.activity(name="request_approval_activity")
def request_approval_activity(ctx: WorkflowActivityContext,
input: OrderPayload):
"""Defines Request Approval Activity. This is used by the workflow to request approval
for payment of an order. This activity is used only if the order total cost is greater than
a particular threshold"""
logger = logging.getLogger('RequestApprovalActivity')
logger.info('Requesting approval for payment of '+f'{input["total_cost"]}'+' USD for '
+f'{input["quantity"]}' +' ' +f'{input["item_name"]}')
order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。该工作流由四个工作流活动或任务组成:
notifyActivity:使用记录器在工作流过程中输出消息。这些消息会在库存不足、付款无法处理等情况时通知用户。verifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。requestApprovalActivity:请求对超过特定阈值的订单进行批准。processPaymentActivity:处理并授权付款。updateInventoryActivity:使用新的剩余库存值更新状态存储。对于此示例,你需要:
克隆 快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在新的终端窗口中,导航到 order-processor 目录:
cd workflows/javascript/sdk/order-processor
安装依赖项:
cd ./javascript/sdk
npm install
npm run build
在终端中,使用 多应用运行 启动订单处理应用程序和 Dapr 边车。从 javascript/sdk 目录,运行以下命令:
dapr run -f .
这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。
预期输出:
== APP - order-processor == Starting new orderProcessingWorkflow instance with ID = f5087775-779c-4e73-ac77-08edfcb375f4
== APP - order-processor == Orchestration scheduled with ID: f5087775-779c-4e73-ac77-08edfcb375f4
== APP - order-processor == Waiting 30 seconds for instance f5087775-779c-4e73-ac77-08edfcb375f4 to complete...
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 0 history event...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1]
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Received order f5087775-779c-4e73-ac77-08edfcb375f4 for 1 car at a total cost of 5000
== APP - order-processor == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 3 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Verifying inventory for f5087775-779c-4e73-ac77-08edfcb375f4 of 1 car
== APP - order-processor == 2025-02-13T10:33:21.622Z INFO [HTTPClient, HTTPClient] Sidecar Started
== APP - order-processor == There are 10 car in stock
== APP - order-processor == Activity verifyInventoryActivity completed with output {"success":true,"inventoryItem":{"itemName":"car","perItemCost":5000,"quantity":10}} (84 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 6 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Processing payment for order car
== APP - order-processor == Payment of 5000 for 1 car processed successfully
== APP - order-processor == Activity processPaymentActivity completed with output true (4 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 9 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == Updating inventory for f5087775-779c-4e73-ac77-08edfcb375f4 of 1 car
== APP - order-processor == Inventory updated for f5087775-779c-4e73-ac77-08edfcb375f4, there are now 9 car in stock
== APP - order-processor == Activity updateInventoryActivity completed with output {"success":true,"inventoryItem":{"itemName":"car","perItemCost":5000,"quantity":9}} (83 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 12 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Received "Activity Request" work item
== APP - order-processor == order f5087775-779c-4e73-ac77-08edfcb375f4 processed successfully!
== APP - order-processor == Activity notifyActivity completed with output undefined (0 chars)
== APP - order-processor == Received "Orchestrator Request" work item with instance id 'f5087775-779c-4e73-ac77-08edfcb375f4'
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Rebuilding local state with 15 history event...
== APP - order-processor == Processing order f5087775-779c-4e73-ac77-08edfcb375f4...
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - order-processor == Order f5087775-779c-4e73-ac77-08edfcb375f4 processed successfully!
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Orchestration completed with status COMPLETED
== APP - order-processor == f5087775-779c-4e73-ac77-08edfcb375f4: Returning 1 action(s)
== APP - order-processor == Instance f5087775-779c-4e73-ac77-08edfcb375f4 completed
== APP - order-processor == Orchestration completed! Result: {"processed":true}
运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:
docker run -d -p 9411:9411 openzipkin/zipkin
在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

当你运行 dapr run -f . 时:
f5087775-779c-4e73-ac77-08edfcb375f4)并调度了工作流。notifyActivity 工作流活动发送一条通知,说明已收到 1 辆汽车的订单。verifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。requestApprovalActivity 工作流活动请求对订单 f5087775-779c-4e73-ac77-08edfcb375f4 的批准processPaymentActivity 工作流活动开始处理订单 f5087775-779c-4e73-ac77-08edfcb375f4 的付款并确认是否成功。updateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。notifyActivity 工作流活动发送一条通知,说明订单 f5087775-779c-4e73-ac77-08edfcb375f4 已完成并已处理。order-processor/app.ts在应用程序文件中:
import { DaprWorkflowClient, WorkflowRuntime, DaprClient, CommunicationProtocolEnum } from "@dapr/dapr";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, verifyInventoryActivity as verifyInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";
const workflowWorker = new WorkflowRuntime();
async function start() {
// Update the gRPC client and worker to use a local address and port
const workflowClient = new DaprWorkflowClient();
const daprHost = process.env.DAPR_HOST ?? "127.0.0.1";
const daprPort = process.env.DAPR_GRPC_PORT ?? "50001";
const daprClient = new DaprClient({
daprHost,
daprPort,
communicationProtocol: CommunicationProtocolEnum.GRPC,
});
const storeName = "statestore";
const inventory = new InventoryItem("car", 5000, 10);
const key = inventory.itemName;
await daprClient.state.save(storeName, [
{
key: key,
value: inventory,
}
]);
const order = new OrderPayload("car", 5000, 1);
workflowWorker
.registerWorkflow(orderProcessingWorkflow)
.registerActivity(notifyActivity)
.registerActivity(verifyInventoryActivity)
.registerActivity(requestApprovalActivity)
.registerActivity(processPaymentActivity)
.registerActivity(updateInventoryActivity);
// Wrap the worker startup in a try-catch block to handle any errors during startup
try {
await workflowWorker.start();
console.log("Workflow runtime started successfully");
} catch (error) {
console.error("Error starting workflow runtime:", error);
}
// Schedule a new orchestration
try {
const id = await workflowClient.scheduleNewWorkflow(orderProcessingWorkflow, order);
console.log(`Orchestration scheduled with ID: ${id}`);
// Wait for orchestration completion
const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30);
console.log(`Orchestration completed! Result: ${state?.serializedOutput}`);
} catch (error) {
console.error("Error scheduling or waiting for orchestration:", error);
throw error;
}
await workflowClient.stop();
}
process.on('SIGTERM', () => {
workflowWorker.stop();
})
start().catch((e) => {
console.error(e);
process.exit(1);
});
order-processor/orderProcessingWorkflow.ts在 orderProcessingWorkflow.ts 中,工作流被定义为一个类,其中包含所有相关任务(由工作流活动确定)。
import { Task, WorkflowActivityContext, WorkflowContext, TWorkflow, DaprClient } from "@dapr/dapr";
import { InventoryItem, InventoryRequest, InventoryResult, OrderNotification, OrderPayload, OrderPaymentRequest, OrderResult } from "./model";
const daprClient = new DaprClient();
const storeName = "statestore";
// Defines Notify Activity. This is used by the workflow to send out a notification
export const notifyActivity = async (_: WorkflowActivityContext, orderNotification: OrderNotification) => {
console.log(orderNotification.message);
return;
};
//Defines Verify Inventory Activity. This is used by the workflow to verify if inventory is available for the order
export const verifyInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Verifying inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
const result = await daprClient.state.get(storeName, inventoryRequest.itemName);
if (result == undefined || result == null) {
return new InventoryResult(false, undefined);
}
const inventoryItem = result as InventoryItem;
console.log(`There are ${inventoryItem.quantity} ${inventoryItem.itemName} in stock`);
if (inventoryItem.quantity >= inventoryRequest.quantity) {
return new InventoryResult(true, inventoryItem)
}
return new InventoryResult(false, undefined);
}
export const requestApprovalActivity = async (_: WorkflowActivityContext, orderPayLoad: OrderPayload) => {
console.log(`Requesting approval for order ${orderPayLoad.itemName}`);
return true;
}
export const processPaymentActivity = async (_: WorkflowActivityContext, orderPaymentRequest: OrderPaymentRequest) => {
console.log(`Processing payment for order ${orderPaymentRequest.itemBeingPurchased}`);
console.log(`Payment of ${orderPaymentRequest.amount} for ${orderPaymentRequest.quantity} ${orderPaymentRequest.itemBeingPurchased} processed successfully`);
return true;
}
export const updateInventoryActivity = async (_: WorkflowActivityContext, inventoryRequest: InventoryRequest) => {
console.log(`Updating inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
const result = await daprClient.state.get(storeName, inventoryRequest.itemName);
if (result == undefined || result == null) {
return new InventoryResult(false, undefined);
}
const inventoryItem = result as InventoryItem;
inventoryItem.quantity = inventoryItem.quantity - inventoryRequest.quantity;
if (inventoryItem.quantity < 0) {
console.log(`Insufficient inventory for ${inventoryRequest.requestId} of ${inventoryRequest.quantity} ${inventoryRequest.itemName}`);
return new InventoryResult(false, undefined);
}
await daprClient.state.save(storeName, [
{
key: inventoryRequest.itemName,
value: inventoryItem,
}
]);
console.log(`Inventory updated for ${inventoryRequest.requestId}, there are now ${inventoryItem.quantity} ${inventoryItem.itemName} in stock`);
return new InventoryResult(true, inventoryItem);
}
export const orderProcessingWorkflow: TWorkflow = async function* (ctx: WorkflowContext, orderPayLoad: OrderPayload): any {
const orderId = ctx.getWorkflowInstanceId();
console.log(`Processing order ${orderId}...`);
const orderNotification: OrderNotification = {
message: `Received order ${orderId} for ${orderPayLoad.quantity} ${orderPayLoad.itemName} at a total cost of ${orderPayLoad.totalCost}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
const inventoryRequest = new InventoryRequest(orderId, orderPayLoad.itemName, orderPayLoad.quantity);
const inventoryResult = yield ctx.callActivity(verifyInventoryActivity, inventoryRequest);
if (!inventoryResult.success) {
const orderNotification: OrderNotification = {
message: `Insufficient inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
if (orderPayLoad.totalCost > 5000) {
yield ctx.callActivity(requestApprovalActivity, orderPayLoad);
const tasks: Task<any>[] = [];
const approvalEvent = ctx.waitForExternalEvent("approval_event");
tasks.push(approvalEvent);
const timeOutEvent = ctx.createTimer(30);
tasks.push(timeOutEvent);
const winner = ctx.whenAny(tasks);
if (winner == timeOutEvent) {
const orderNotification: OrderNotification = {
message: `Order ${orderId} has been cancelled due to approval timeout.`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
const approvalResult = approvalEvent.getResult();
if (!approvalResult) {
const orderNotification: OrderNotification = {
message: `Order ${orderId} was not approved.`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
}
const orderPaymentRequest = new OrderPaymentRequest(orderId, orderPayLoad.itemName, orderPayLoad.totalCost, orderPayLoad.quantity);
const paymentResult = yield ctx.callActivity(processPaymentActivity, orderPaymentRequest);
if (!paymentResult) {
const orderNotification: OrderNotification = {
message: `Payment for order ${orderId} failed`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
const updatedResult = yield ctx.callActivity(updateInventoryActivity, inventoryRequest);
if (!updatedResult.success) {
const orderNotification: OrderNotification = {
message: `Failed to update inventory for order ${orderId}`,
};
yield ctx.callActivity(notifyActivity, orderNotification);
return new OrderResult(false);
}
const orderCompletedNotification: OrderNotification = {
message: `order ${orderId} processed successfully!`,
};
yield ctx.callActivity(notifyActivity, orderCompletedNotification);
console.log(`Order ${orderId} processed successfully!`);
return new OrderResult(true);
}
order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。该工作流由四个工作流活动或任务组成:
NotifyActivity:使用记录器在工作流过程中输出消息VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。RequestApprovalActivity:请求对超过特定阈值的订单进行批准。ProcessPaymentActivity:处理并授权付款。UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。对于此示例,你需要:
注意:.NET 7 是 Dapr v1.15 中 Dapr.Workflows 支持的最低 .NET 版本。在 Dapr v1.16 及更高版本中,仅支持 .NET 8 和 .NET 9。
克隆 快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在新的终端窗口中,导航到 order-processor 目录:
cd workflows/csharp/sdk/order-processor
安装依赖项:
dotnet restore
dotnet build
返回到 csharp/sdk 目录:
cd ..
在终端中,使用 多应用运行 启动订单处理应用程序和 Dapr 边车。从 csharp/sdk 目录,运行以下命令:
dapr run -f .
这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。
预期输出:
== APP - order-processor == Starting workflow 571a6e25 purchasing 1 Cars
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[40]
== APP - order-processor == Scheduling new OrderProcessingWorkflow orchestration with instance ID '571a6e25' and 45 bytes of input data.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/StartInstance
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/StartInstance
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 3045.9209ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3046.0945ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 3016.1346ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3016.3572ms - 200
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[42]
== APP - order-processor == Waiting for instance '571a6e25' to start.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceStart
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceStart
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 2.9095ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 3.0445ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 99.446ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 99.5407ms - 200
== APP - order-processor == Your workflow has started. Here is the status of the workflow: Running
== APP - order-processor == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[43]
== APP - order-processor == Waiting for instance '571a6e25' to complete, fail, or terminate.
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceCompletion
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/WaitForInstanceCompletion
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Received order 571a6e25 for 1 Cars at $5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.6785ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.7869ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[2013970020]
== APP - order-processor == Received request ID '571a6e25' for 1 Cars at $5000
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.1947ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.3293ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.VerifyInventoryActivity[1478802116]
== APP - order-processor == Reserving inventory for order request ID '571a6e25' of 1 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.VerifyInventoryActivity[1130866279]
== APP - order-processor == There are: 10 Cars available for purchase
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.8534ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 2.0077ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[1162731597]
== APP - order-processor == Checked inventory for request ID 'InventoryRequest { RequestId = 571a6e25, ItemName = Cars, Quantity = 1 }'
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.1851ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.3742ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[340284070]
== APP - order-processor == Processing payment: request ID '571a6e25' for 1 Cars at $5000
== APP - order-processor == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[1851315765]
== APP - order-processor == Payment for request ID '571a6e25' processed successfully
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.8249ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.9595ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[340284070]
== APP - order-processor == Processed payment request as there's sufficient inventory to proceed: PaymentRequest { RequestId = 571a6e25, ItemBeingPurchased = Cars, Amount = 1, Currency = 5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.4457ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5267ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[2144991393]
== APP - order-processor == Checking inventory for request ID '571a6e25' for 1 Cars
== APP - order-processor == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[1901852920]
== APP - order-processor == There are now 9 Cars left in stock
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.6012ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.7097ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[96138418]
== APP - order-processor == Updating available inventory for PaymentRequest { RequestId = 571a6e25, ItemBeingPurchased = Cars, Amount = 1, Currency = 5000 }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.469ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5431ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Activities.NotifyActivity[1985924262]
== APP - order-processor == Presenting notification Notification { Message = Order 571a6e25 has completed! }
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteActivityTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 0.494ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 0.5685ms - 200
== APP - order-processor == info: WorkflowConsoleApp.Workflows.OrderProcessingWorkflow[510392223]
== APP - order-processor == Order 571a6e25 has completed
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
== APP - order-processor == Start processing HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[100]
== APP - order-processor == Sending HTTP request POST http://localhost:37355/TaskHubSidecarService/CompleteOrchestratorTask
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 1.6353ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 1.7546ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.ClientHandler[101]
== APP - order-processor == Received HTTP response headers after 15807.213ms - 200
== APP - order-processor == info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
== APP - order-processor == End processing HTTP request after 15807.3675ms - 200
== APP - order-processor == Workflow Status: Completed
运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:
docker run -d -p 9411:9411 openzipkin/zipkin
在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

当你运行 dapr run -f . 时:
571a6e25)并调度了工作流。NotifyActivity 工作流活动发送一条通知,说明已收到一辆汽车的订单。VerifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。RequestApprovalActivity 活动。ProcessPaymentActivity 工作流活动开始处理订单 571a6e25 的付款并确认是否成功。UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。NotifyActivity 工作流活动发送一条通知,说明订单 571a6e25 已完成。order-processor/Program.cs在应用程序的程序文件中:
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using WorkflowConsoleApp.Activities;
using WorkflowConsoleApp.Models;
using WorkflowConsoleApp.Workflows;
const string storeName = "statestore";
// The workflow host is a background service that connects to the sidecar over gRPC
var builder = Host.CreateDefaultBuilder(args).ConfigureServices(services =>
{
services.AddDaprClient();
services.AddDaprWorkflow(options =>
{
// Note that it's also possible to register a lambda function as the workflow
// or activity implementation instead of a class.
options.RegisterWorkflow<OrderProcessingWorkflow>();
// These are the activities that get invoked by the workflow(s).
options.RegisterActivity<NotifyActivity>();
options.RegisterActivity<VerifyInventoryActivity>();
options.RegisterActivity<RequestApprovalActivity>();
options.RegisterActivity<ProcessPaymentActivity>();
options.RegisterActivity<UpdateInventoryActivity>();
});
});
// Start the app - this is the point where we connect to the Dapr sidecar
using var host = builder.Build();
host.Start();
var daprClient = host.Services.GetRequiredService<DaprClient>();
var workflowClient = host.Services.GetRequiredService<DaprWorkflowClient>();
// Generate a unique ID for the workflow
var orderId = Guid.NewGuid().ToString()[..8];
const string itemToPurchase = "Cars";
const int amountToPurchase = 1;
// Populate the store with items
RestockInventory(itemToPurchase);
// Construct the order
var orderInfo = new OrderPayload(itemToPurchase, 5000, amountToPurchase);
// Start the workflow
Console.WriteLine($"Starting workflow {orderId} purchasing {amountToPurchase} {itemToPurchase}");
await workflowClient.ScheduleNewWorkflowAsync(
name: nameof(OrderProcessingWorkflow),
instanceId: orderId,
input: orderInfo);
// Wait for the workflow to start and confirm the input
var state = await workflowClient.WaitForWorkflowStartAsync(
instanceId: orderId);
Console.WriteLine($"Your workflow has started. Here is the status of the workflow: {Enum.GetName(typeof(WorkflowRuntimeStatus), state.RuntimeStatus)}");
// Wait for the workflow to complete
state = await workflowClient.WaitForWorkflowCompletionAsync(
instanceId: orderId);
Console.WriteLine("Workflow Status: {0}", Enum.GetName(typeof(WorkflowRuntimeStatus), state.RuntimeStatus));
return;
void RestockInventory(string itemToPurchase)
{
daprClient.SaveStateAsync(storeName, itemToPurchase, new OrderPayload(Name: itemToPurchase, TotalCost: 50000, Quantity: 10));
}
order-processor/Workflows/OrderProcessingWorkflow.cs在 OrderProcessingWorkflow.cs 中,工作流被定义为一个类,其中包含所有相关任务(由单独文件中的工作流活动确定)。
namespace WorkflowConsoleApp.Workflows;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using Dapr.Workflow;
using DurableTask.Core.Exceptions;
using Activities;
using Models;
internal sealed partial class OrderProcessingWorkflow : Workflow<OrderPayload, OrderResult>
{
public override async Task<OrderResult> RunAsync(WorkflowContext context, OrderPayload order)
{
var logger = context.CreateReplaySafeLogger<OrderProcessingWorkflow>();
var orderId = context.InstanceId;
// Notify the user that an order has come through
await context.CallActivityAsync(nameof(NotifyActivity),
new Notification($"Received order {orderId} for {order.Quantity} {order.Name} at ${order.TotalCost}"));
LogOrderReceived(logger, orderId, order.Quantity, order.Name, order.TotalCost);
// Determine if there is enough of the item available for purchase by checking the inventory
var inventoryRequest = new InventoryRequest(RequestId: orderId, order.Name, order.Quantity);
var result = await context.CallActivityAsync<InventoryResult>(
nameof(VerifyInventoryActivity), inventoryRequest);
LogCheckInventory(logger, inventoryRequest);
// If there is insufficient inventory, fail and let the user know
if (!result.Success)
{
// End the workflow here since we don't have sufficient inventory
await context.CallActivityAsync(nameof(NotifyActivity),
new Notification($"Insufficient inventory for {order.Name}"));
LogInsufficientInventory(logger, order.Name);
return new OrderResult(Processed: false);
}
if (order.TotalCost > 5000)
{
await context.CallActivityAsync(nameof(RequestApprovalActivity),
new ApprovalRequest(orderId, order.Name, order.Quantity, order.TotalCost));
var approvalResponse = await context.WaitForExternalEventAsync<ApprovalResponse>(
eventName: "ApprovalEvent",
timeout: TimeSpan.FromSeconds(30));
if (!approvalResponse.IsApproved)
{
await context.CallActivityAsync(nameof(NotifyActivity),
new Notification($"Order {orderId} was not approved"));
LogOrderNotApproved(logger, orderId);
return new OrderResult(Processed: false);
}
}
// There is enough inventory available so the user can purchase the item(s). Process their payment
var processPaymentRequest = new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost);
await context.CallActivityAsync(nameof(ProcessPaymentActivity),processPaymentRequest);
LogPaymentProcessing(logger, processPaymentRequest);
try
{
// Update the available inventory
var paymentRequest = new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost);
await context.CallActivityAsync(nameof(UpdateInventoryActivity), paymentRequest);
LogInventoryUpdate(logger, paymentRequest);
}
catch (TaskFailedException)
{
// Let them know their payment was processed, but there's insufficient inventory, so they're getting a refund
await context.CallActivityAsync(nameof(NotifyActivity),
new Notification($"Order {orderId} Failed! You are now getting a refund"));
LogRefund(logger, orderId);
return new OrderResult(Processed: false);
}
// Let them know their payment was processed
await context.CallActivityAsync(nameof(NotifyActivity), new Notification($"Order {orderId} has completed!"));
LogSuccessfulOrder(logger, orderId);
// End the workflow with a success result
return new OrderResult(Processed: true);
}
[LoggerMessage(LogLevel.Information, "Received request ID '{request}' for {quantity} {name} at ${totalCost}")]
static partial void LogOrderReceived(ILogger logger, string request, int quantity, string name, double totalCost);
[LoggerMessage(LogLevel.Information, "Checked inventory for request ID '{request}'")]
static partial void LogCheckInventory(ILogger logger, InventoryRequest request);
[LoggerMessage(LogLevel.Information, "Insufficient inventory for order {orderName}")]
static partial void LogInsufficientInventory(ILogger logger, string orderName);
[LoggerMessage(LogLevel.Information, "Order {orderName} was not approved")]
static partial void LogOrderNotApproved(ILogger logger, string orderName);
[LoggerMessage(LogLevel.Information, "Processed payment request as there's sufficient inventory to proceed: {request}")]
static partial void LogPaymentProcessing(ILogger logger, PaymentRequest request);
[LoggerMessage(LogLevel.Information, "Updating available inventory for {request}")]
static partial void LogInventoryUpdate(ILogger logger, PaymentRequest request);
[LoggerMessage(LogLevel.Information, "Order {orderId} failed due to insufficient inventory - processing refund")]
static partial void LogRefund(ILogger logger, string orderId);
[LoggerMessage(LogLevel.Information, "Order {orderId} has completed")]
static partial void LogSuccessfulOrder(ILogger logger, string orderId);
}
order-processor/Activities 目录Activities 目录保存工作流使用的四个活动,在以下文件中定义:
NotifyActivity.csVerifyInventoryActivity.csRequestApprovalActivity.csProcessPaymentActivity.csUpdateInventoryActivity.csorder-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。该工作流由四个工作流活动或任务组成:
NotifyActivity:使用记录器在工作流过程中输出消息。RequestApprovalActivity:请求对超过特定成本阈值的订单进行批准。VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。ProcessPaymentActivity:处理并授权付款。UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。对于此示例,你需要:
克隆 快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
导航到 order-processor 目录:
cd workflows/java/sdk/order-processor
安装依赖项:
mvn clean install
返回到 java/sdk 目录:
cd ..
在终端中,使用 多应用运行 启动订单处理应用程序和 Dapr 边车。从 java/sdk 目录,运行以下命令:
cd workflows/java/sdk
dapr run -f .
这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。
预期输出:
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Workflow: OrderProcessingWorkflow
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: NotifyActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: ProcessPaymentActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: RequestApprovalActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: VerifyInventoryActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Registered Activity: UpdateInventoryActivity
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - List of registered workflows: [io.dapr.quickstarts.workflows.OrderProcessingWorkflow]
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - List of registered activites: [io.dapr.quickstarts.workflows.activities.NotifyActivity, io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity, io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity, io.dapr.quickstarts.workflows.activities.RequestApprovalActivity, io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity]
== APP - order-processor == [main] INFO io.dapr.workflows.runtime.WorkflowRuntimeBuilder - Successfully built dapr workflow runtime
== APP - order-processor == Start workflow runtime
== APP - order-processor == Feb 12, 2025 2:44:13 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock
== APP - order-processor == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:39261.
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == Starting order workflow, purchasing 1 of cars
== APP - order-processor == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.quickstarts.workflows.OrderProcessingWorkflow
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2025-02-12T14:44:18.154Z
== APP - order-processor == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=5000, quantity=1]
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=5000, quantity=1]
== APP - order-processor == workflow instance d1bf548b-c854-44af-978e-90c61ed88e3c started
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - Verifying inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - There are 10 cars available for purchase
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity - Verified inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: d1bf548b-c854-44af-978e-90c61ed88e3c for 1 cars at $5000
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID 'd1bf548b-c854-44af-978e-90c61ed88e3c' processed successfully
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c' of 1 cars
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order 'd1bf548b-c854-44af-978e-90c61ed88e3c': there are now 9 cars left in stock
== APP - order-processor == there are now 9 cars left in stock
== APP - order-processor == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : d1bf548b-c854-44af-978e-90c61ed88e3c
== APP - order-processor == workflow instance completed, out is: {"processed":true}
运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:
docker run -d -p 9411:9411 openzipkin/zipkin
在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

当你运行 dapr run -f . 时:
d1bf548b-c854-44af-978e-90c61ed88e3c)并调度了工作流。NotifyActivity 工作流活动发送一条通知,说明已收到一辆汽车的订单。VertifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。RequestApprovalActivity 活动。ProcessPaymentActivity 工作流活动开始处理订单 d1bf548b-c854-44af-978e-90c61ed88e3c 的付款并确认是否成功。UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。NotifyActivity 工作流活动发送一条通知,说明订单 d1bf548b-c854-44af-978e-90c61ed88e3c 已完成。order-processor/WorkflowConsoleApp.java在应用程序的程序文件中:
package io.dapr.quickstarts.workflows;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.quickstarts.workflows.activities.NotifyActivity;
import io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity;
import io.dapr.quickstarts.workflows.activities.RequestApprovalActivity;
import io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity;
import io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity;
import io.dapr.quickstarts.workflows.models.InventoryItem;
import io.dapr.quickstarts.workflows.models.OrderPayload;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
public class WorkflowConsoleApp {
private static final String STATE_STORE_NAME = "statestore";
/**
* The main method of this console app.
*
* @param args The port the app will listen on.
* @throws Exception An Exception.
*/
public static void main(String[] args) throws Exception {
System.out.println("*** Welcome to the Dapr Workflow console app sample!");
System.out.println("*** Using this app, you can place orders that start workflows.");
// Wait for the sidecar to become available
Thread.sleep(5 * 1000);
// Register the OrderProcessingWorkflow and its activities with the builder.
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class);
builder.registerActivity(NotifyActivity.class);
builder.registerActivity(ProcessPaymentActivity.class);
builder.registerActivity(RequestApprovalActivity.class);
builder.registerActivity(VerifyInventoryActivity.class);
builder.registerActivity(UpdateInventoryActivity.class);
// Build and then start the workflow runtime pulling and executing tasks
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("Start workflow runtime");
runtime.start(false);
}
InventoryItem inventory = prepareInventoryAndOrder();
DaprWorkflowClient workflowClient = new DaprWorkflowClient();
try (workflowClient) {
executeWorkflow(workflowClient, inventory);
}
}
private static void executeWorkflow(DaprWorkflowClient workflowClient, InventoryItem inventory) {
System.out.println("==========Begin the purchase of item:==========");
String itemName = inventory.getName();
int orderQuantity = inventory.getQuantity();
int totalcost = orderQuantity * inventory.getPerItemCost();
OrderPayload order = new OrderPayload();
order.setItemName(itemName);
order.setQuantity(orderQuantity);
order.setTotalCost(totalcost);
System.out.println("Starting order workflow, purchasing " + orderQuantity + " of " + itemName);
String instanceId = workflowClient.scheduleNewWorkflow(OrderProcessingWorkflow.class, order);
System.out.printf("scheduled new workflow instance of OrderProcessingWorkflow with instance ID: %s%n",
instanceId);
try {
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
System.out.printf("workflow instance %s started%n", instanceId);
} catch (TimeoutException e) {
System.out.printf("workflow instance %s did not start within 10 seconds%n", instanceId);
return;
}
try {
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId,
Duration.ofSeconds(30),
true);
if (workflowStatus != null) {
System.out.printf("workflow instance completed, out is: %s%n",
workflowStatus.getSerializedOutput());
} else {
System.out.printf("workflow instance %s not found%n", instanceId);
}
} catch (TimeoutException e) {
System.out.printf("workflow instance %s did not complete within 30 seconds%n", instanceId);
}
}
private static InventoryItem prepareInventoryAndOrder() {
// prepare 10 cars in inventory
InventoryItem inventory = new InventoryItem();
inventory.setName("cars");
inventory.setPerItemCost(50000);
inventory.setQuantity(10);
DaprClient daprClient = new DaprClientBuilder().build();
restockInventory(daprClient, inventory);
// prepare order for 10 cars
InventoryItem order = new InventoryItem();
order.setName("cars");
order.setPerItemCost(5000);
order.setQuantity(1);
return order;
}
private static void restockInventory(DaprClient daprClient, InventoryItem inventory) {
String key = inventory.getName();
daprClient.saveState(STATE_STORE_NAME, key, inventory).block();
}
}
OrderProcessingWorkflow.java在 OrderProcessingWorkflow.java 中,工作流被定义为一个类,其中包含所有相关任务(由工作流活动确定)。
package io.dapr.quickstarts.workflows;
import java.time.Duration;
import org.slf4j.Logger;
import io.dapr.quickstarts.workflows.activities.NotifyActivity;
import io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity;
import io.dapr.quickstarts.workflows.activities.RequestApprovalActivity;
import io.dapr.quickstarts.workflows.activities.VerifyInventoryActivity;
import io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity;
import io.dapr.quickstarts.workflows.models.ApprovalResponse;
import io.dapr.quickstarts.workflows.models.InventoryRequest;
import io.dapr.quickstarts.workflows.models.InventoryResult;
import io.dapr.quickstarts.workflows.models.Notification;
import io.dapr.quickstarts.workflows.models.OrderPayload;
import io.dapr.quickstarts.workflows.models.OrderResult;
import io.dapr.quickstarts.workflows.models.PaymentRequest;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
public class OrderProcessingWorkflow extends Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
Logger logger = ctx.getLogger();
String orderId = ctx.getInstanceId();
logger.info("Starting Workflow: " + ctx.getName());
logger.info("Instance ID(order ID): " + orderId);
logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
OrderPayload order = ctx.getInput(OrderPayload.class);
logger.info("Received Order: " + order.toString());
OrderResult orderResult = new OrderResult();
orderResult.setProcessed(false);
// Notify the user that an order has come through
Notification notification = new Notification();
notification.setMessage("Received Order: " + order.toString());
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
// Determine if there is enough of the item available for purchase by checking
// the inventory
InventoryRequest inventoryRequest = new InventoryRequest();
inventoryRequest.setRequestId(orderId);
inventoryRequest.setItemName(order.getItemName());
inventoryRequest.setQuantity(order.getQuantity());
InventoryResult inventoryResult = ctx.callActivity(VerifyInventoryActivity.class.getName(),
inventoryRequest, InventoryResult.class).await();
// If there is insufficient inventory, fail and let the user know
if (!inventoryResult.isSuccess()) {
notification.setMessage("Insufficient inventory for order : " + order.getItemName());
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
ctx.complete(orderResult);
return;
}
// Require orders over a certain threshold to be approved
if (order.getTotalCost() > 5000) {
ctx.callActivity(RequestApprovalActivity.class.getName(), order).await();
ApprovalResponse approvalResponse = ctx.waitForExternalEvent("approvalEvent",
Duration.ofSeconds(30), ApprovalResponse.class).await();
if (!approvalResponse.isApproved()) {
notification.setMessage("Order " + order.getItemName() + " was not approved.");
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
ctx.complete(orderResult);
return;
}
}
// There is enough inventory available so the user can purchase the item(s).
// Process their payment
PaymentRequest paymentRequest = new PaymentRequest();
paymentRequest.setRequestId(orderId);
paymentRequest.setItemBeingPurchased(order.getItemName());
paymentRequest.setQuantity(order.getQuantity());
paymentRequest.setAmount(order.getTotalCost());
boolean isOK = ctx.callActivity(ProcessPaymentActivity.class.getName(),
paymentRequest, boolean.class).await();
if (!isOK) {
notification.setMessage("Payment failed for order : " + orderId);
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
ctx.complete(orderResult);
return;
}
inventoryResult = ctx.callActivity(UpdateInventoryActivity.class.getName(),
inventoryRequest, InventoryResult.class).await();
if (!inventoryResult.isSuccess()) {
// If there is an error updating the inventory, refund the user
// paymentRequest.setAmount(-1 * paymentRequest.getAmount());
// ctx.callActivity(ProcessPaymentActivity.class.getName(),
// paymentRequest).await();
// Let users know their payment processing failed
notification.setMessage("Order failed to update inventory! : " + orderId);
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
ctx.complete(orderResult);
return;
}
// Let user know their order was processed
notification.setMessage("Order completed! : " + orderId);
ctx.callActivity(NotifyActivity.class.getName(), notification).await();
// Complete the workflow with order result is processed
orderResult.setProcessed(true);
ctx.complete(orderResult);
};
}
}
activities 目录Activities 目录保存工作流使用的四个活动,在以下文件中定义:
order-processor 控制台应用程序启动并管理 OrderProcessingWorkflow 工作流,它模拟从商店购买商品。该工作流由五个独特的工作流活动或任务组成:
NotifyActivity:使用记录器在工作流过程中输出消息。这些消息会在以下情况下通知你:VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。RequestApprovalActivity:请求对超过特定成本阈值的订单进行批准。ProcessPaymentActivity:处理并授权付款。UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。对于此示例,你需要:
克隆 快速入门仓库中提供的示例。
git clone https://github.com/dapr/quickstarts.git
在新的终端窗口中,导航到 sdk 目录:
cd workflows/go/sdk
在终端中,使用 多应用运行 启动订单处理应用程序和 Dapr 边车。从 go/sdk 目录,运行以下命令:
dapr run -f .
这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。
预期输出:
== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == dapr client initializing for: 127.0.0.1:46533
== APP - order-processor == INFO: 2025/02/13 13:18:33 connecting work item listener stream
== APP - order-processor == 2025/02/13 13:18:33 work item listener started
== APP - order-processor == INFO: 2025/02/13 13:18:33 starting background processor
== APP - order-processor == adding base stock item: paperclip
== APP - order-processor == adding base stock item: cars
== APP - order-processor == adding base stock item: computers
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == NotifyActivity: Received order b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 cars - $5000
== APP - order-processor == VerifyInventoryActivity: Verifying inventory for order b4cb2687-1af0-4f8d-9659-eb6389c07ade of 1 cars
== APP - order-processor == VerifyInventoryActivity: There are 10 cars available for purchase
== APP - order-processor == ProcessPaymentActivity: b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 - cars (5000USD)
== APP - order-processor == UpdateInventoryActivity: Checking Inventory for order b4cb2687-1af0-4f8d-9659-eb6389c07ade for 1 * cars
== APP - order-processor == UpdateInventoryActivity: There are now 9 cars left in stock
== APP - order-processor == NotifyActivity: Order b4cb2687-1af0-4f8d-9659-eb6389c07ade has completed!
== APP - order-processor == workflow status: COMPLETED
== APP - order-processor == Purchase of item is complete
使用 CTRL+C 或以下命令停止 Dapr 工作流:
dapr stop -f .
运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:
docker run -d -p 9411:9411 openzipkin/zipkin
在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

当你运行 dapr run 时:
b4cb2687-1af0-4f8d-9659-eb6389c07ade)并调度了工作流。NotifyActivity 工作流活动发送一条通知,说明已收到 10 辆汽车的订单。VerifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。RequestApprovalActivity 活动。ProcessPaymentActivity 工作流活动开始处理订单 b4cb2687-1af0-4f8d-9659-eb6389c07ade 的付款并确认是否成功。UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。NotifyActivity 工作流活动发送一条通知,说明订单 b4cb2687-1af0-4f8d-9659-eb6389c07ade 已完成。order-processor/main.go在应用程序的程序文件中:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/dapr/durabletask-go/workflow"
"github.com/dapr/go-sdk/client"
)
var (
stateStoreName = "statestore"
workflowComponent = "dapr"
workflowName = "OrderProcessingWorkflow"
defaultItemName = "cars"
)
func main() {
fmt.Println("*** Welcome to the Dapr Workflow console app sample!")
fmt.Println("*** Using this app, you can place orders that start workflows.")
r := workflow.NewRegistry()
if err := r.AddWorkflow(OrderProcessingWorkflow); err != nil {
log.Fatal(err)
}
if err := r.AddActivity(NotifyActivity); err != nil {
log.Fatal(err)
}
if err := r.AddActivity(RequestApprovalActivity); err != nil {
log.Fatal(err)
}
if err := r.AddActivity(VerifyInventoryActivity); err != nil {
log.Fatal(err)
}
if err := r.AddActivity(ProcessPaymentActivity); err != nil {
log.Fatal(err)
}
if err := r.AddActivity(UpdateInventoryActivity); err != nil {
log.Fatal(err)
}
wfClient, err := client.NewWorkflowClient()
if err != nil {
log.Fatalf("failed to initialise workflow client: %v", err)
}
if err := wfClient.StartWorker(context.Background(), r); err != nil {
log.Fatal(err)
}
dclient, err := client.NewClient()
if err != nil {
log.Fatal(err)
}
inventory := []InventoryItem{
{ItemName: "paperclip", PerItemCost: 5, Quantity: 100},
{ItemName: "cars", PerItemCost: 5000, Quantity: 10},
{ItemName: "computers", PerItemCost: 500, Quantity: 100},
}
if err := restockInventory(dclient, inventory); err != nil {
log.Fatalf("failed to restock: %v", err)
}
fmt.Println("==========Begin the purchase of item:==========")
itemName := defaultItemName
orderQuantity := 1
totalCost := inventory[1].PerItemCost * orderQuantity
orderPayload := OrderPayload{
ItemName: itemName,
Quantity: orderQuantity,
TotalCost: totalCost,
}
id, err := wfClient.ScheduleWorkflow(context.Background(), workflowName, workflow.WithInput(orderPayload), workflow.WithInstanceID("order-"+time.Now().Format("20060102150405")))
if err != nil {
log.Fatalf("failed to start workflow: %v", err)
}
waitCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
_, err = wfClient.WaitForWorkflowCompletion(waitCtx, id)
cancel()
if err != nil {
log.Fatalf("failed to wait for workflow: %v", err)
}
respFetch, err := wfClient.FetchWorkflowMetadata(context.Background(), id, workflow.WithFetchPayloads(true))
if err != nil {
log.Fatalf("failed to get workflow: %v", err)
}
fmt.Printf("workflow status: %v\n", respFetch.String())
fmt.Println("Purchase of item is complete")
select {}
}
func restockInventory(daprClient client.Client, inventory []InventoryItem) error {
for _, item := range inventory {
itemSerialized, err := json.Marshal(item)
if err != nil {
return err
}
fmt.Printf("adding base stock item: %s\n", item.ItemName)
if err := daprClient.SaveState(context.Background(), stateStoreName, item.ItemName, itemSerialized, nil); err != nil {
return err
}
}
return nil
}
order-processor/workflow.gopackage main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/dapr/durabletask-go/workflow"
"github.com/dapr/go-sdk/client"
)
type OrderPayload struct {
ItemName string `json:"item_name"`
TotalCost int `json:"total_cost"`
Quantity int `json:"quantity"`
}
type OrderResult struct {
Processed bool `json:"processed"`
}
type InventoryItem struct {
ItemName string `json:"item_name"`
PerItemCost int `json:"per_item_cost"`
Quantity int `json:"quantity"`
}
type InventoryRequest struct {
RequestID string `json:"request_id"`
ItemName string `json:"item_name"`
Quantity int `json:"quantity"`
}
type InventoryResult struct {
Success bool `json:"success"`
InventoryItem InventoryItem `json:"inventory_item"`
}
type PaymentRequest struct {
RequestID string `json:"request_id"`
ItemBeingPurchased string `json:"item_being_purchased"`
Amount int `json:"amount"`
Quantity int `json:"quantity"`
}
type ApprovalRequired struct {
Approval bool `json:"approval"`
}
type Notification struct {
Message string `json:"message"`
}
// OrderProcessingWorkflow is the main workflow for orchestrating activities in the order process.
func OrderProcessingWorkflow(ctx *workflow.WorkflowContext) (any, error) {
orderID := ctx.ID()
var orderPayload OrderPayload
if err := ctx.GetInput(&orderPayload); err != nil {
return nil, err
}
err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{
Message: fmt.Sprintf("Received order %s for %d %s - $%d", orderID, orderPayload.Quantity, orderPayload.ItemName, orderPayload.TotalCost),
})).Await(nil)
if err != nil {
return OrderResult{Processed: false}, err
}
var verifyInventoryResult InventoryResult
if err := ctx.CallActivity(VerifyInventoryActivity, workflow.WithActivityInput(InventoryRequest{
RequestID: orderID,
ItemName: orderPayload.ItemName,
Quantity: orderPayload.Quantity,
})).Await(&verifyInventoryResult); err != nil {
return OrderResult{Processed: false}, err
}
if !verifyInventoryResult.Success {
notification := Notification{Message: fmt.Sprintf("Insufficient inventory for %s", orderPayload.ItemName)}
err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(notification)).Await(nil)
return OrderResult{Processed: false}, err
}
if orderPayload.TotalCost > 5000 {
var approvalRequired ApprovalRequired
if err := ctx.CallActivity(RequestApprovalActivity, workflow.WithActivityInput(orderPayload)).Await(&approvalRequired); err != nil {
return OrderResult{Processed: false}, err
}
if err := ctx.WaitForExternalEvent("manager_approval", time.Second*200).Await(nil); err != nil {
return OrderResult{Processed: false}, err
}
// TODO: Confirm timeout flow - this will be in the form of an error.
if approvalRequired.Approval {
if err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{Message: fmt.Sprintf("Payment for order %s has been approved!", orderID)})).Await(nil); err != nil {
log.Printf("failed to notify of a successful order: %v\n", err)
}
} else {
if err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{Message: fmt.Sprintf("Payment for order %s has been rejected!", orderID)})).Await(nil); err != nil {
log.Printf("failed to notify of an unsuccessful order :%v\n", err)
}
return OrderResult{Processed: false}, err
}
}
err = ctx.CallActivity(ProcessPaymentActivity, workflow.WithActivityInput(PaymentRequest{
RequestID: orderID,
ItemBeingPurchased: orderPayload.ItemName,
Amount: orderPayload.TotalCost,
Quantity: orderPayload.Quantity,
})).Await(nil)
if err != nil {
if err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{Message: fmt.Sprintf("Order %s failed!", orderID)})).Await(nil); err != nil {
log.Printf("failed to notify of a failed order: %v", err)
}
return OrderResult{Processed: false}, err
}
err = ctx.CallActivity(UpdateInventoryActivity, workflow.WithActivityInput(PaymentRequest{
RequestID: orderID,
ItemBeingPurchased: orderPayload.ItemName,
Amount: orderPayload.TotalCost,
Quantity: orderPayload.Quantity,
})).Await(nil)
if err != nil {
if err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{Message: fmt.Sprintf("Order %s failed!", orderID)})).Await(nil); err != nil {
log.Printf("failed to notify of a failed order: %v", err)
}
return OrderResult{Processed: false}, err
}
if err := ctx.CallActivity(NotifyActivity, workflow.WithActivityInput(Notification{Message: fmt.Sprintf("Order %s has completed!", orderID)})).Await(nil); err != nil {
log.Printf("failed to notify of a successful order: %v", err)
}
return OrderResult{Processed: true}, err
}
// NotifyActivity outputs a notification message
func NotifyActivity(ctx workflow.ActivityContext) (any, error) {
var input Notification
if err := ctx.GetInput(&input); err != nil {
return "", err
}
fmt.Printf("NotifyActivity: %s\n", input.Message)
return nil, nil
}
// ProcessPaymentActivity is used to process a payment
func ProcessPaymentActivity(ctx workflow.ActivityContext) (any, error) {
var input PaymentRequest
if err := ctx.GetInput(&input); err != nil {
return "", err
}
fmt.Printf("ProcessPaymentActivity: %s for %d - %s (%dUSD)\n", input.RequestID, input.Quantity, input.ItemBeingPurchased, input.Amount)
return nil, nil
}
// VerifyInventoryActivity is used to verify if an item is available in the inventory
func VerifyInventoryActivity(ctx workflow.ActivityContext) (any, error) {
var input InventoryRequest
if err := ctx.GetInput(&input); err != nil {
return nil, err
}
fmt.Printf("VerifyInventoryActivity: Verifying inventory for order %s of %d %s\n", input.RequestID, input.Quantity, input.ItemName)
dClient, err := client.NewClient()
if err != nil {
return nil, err
}
item, err := dClient.GetState(context.Background(), stateStoreName, input.ItemName, nil)
if err != nil {
return nil, err
}
if item == nil {
return InventoryResult{
Success: false,
InventoryItem: InventoryItem{},
}, nil
}
var result InventoryItem
if err := json.Unmarshal(item.Value, &result); err != nil {
log.Fatalf("failed to parse inventory result %v", err)
}
fmt.Printf("VerifyInventoryActivity: There are %d %s available for purchase\n", result.Quantity, result.ItemName)
if result.Quantity >= input.Quantity {
return InventoryResult{Success: true, InventoryItem: result}, nil
}
return InventoryResult{Success: false, InventoryItem: InventoryItem{}}, nil
}
// UpdateInventoryActivity modifies the inventory.
func UpdateInventoryActivity(ctx workflow.ActivityContext) (any, error) {
var input PaymentRequest
if err := ctx.GetInput(&input); err != nil {
return nil, err
}
fmt.Printf("UpdateInventoryActivity: Checking Inventory for order %s for %d * %s\n", input.RequestID, input.Quantity, input.ItemBeingPurchased)
dClient, err := client.NewClient()
if err != nil {
return nil, err
}
item, err := dClient.GetState(context.Background(), stateStoreName, input.ItemBeingPurchased, nil)
if err != nil {
return nil, err
}
var result InventoryItem
err = json.Unmarshal(item.Value, &result)
if err != nil {
return nil, err
}
newQuantity := result.Quantity - input.Quantity
if newQuantity < 0 {
return nil, fmt.Errorf("insufficient inventory for: %s", input.ItemBeingPurchased)
}
result.Quantity = newQuantity
newState, err := json.Marshal(result)
if err != nil {
log.Fatalf("failed to marshal new state: %v", err)
}
dClient.SaveState(context.Background(), stateStoreName, input.ItemBeingPurchased, newState, nil)
fmt.Printf("UpdateInventoryActivity: There are now %d %s left in stock\n", result.Quantity, result.ItemName)
return InventoryResult{Success: true, InventoryItem: result}, nil
}
// RequestApprovalActivity requests approval for the order
func RequestApprovalActivity(ctx workflow.ActivityContext) (any, error) {
var input OrderPayload
if err := ctx.GetInput(&input); err != nil {
return nil, err
}
fmt.Printf("RequestApprovalActivity: Requesting approval for payment of %dUSD for %d %s\n", input.TotalCost, input.Quantity, input.ItemName)
return ApprovalRequired{Approval: true}, nil
}
现在你的工作流正在运行,让我们学习如何使用 Dapr CLI 来管理它。
打开一个单独的终端并运行以下 CLI 命令。
# 列出所有工作流
dapr workflow list --app-id order-processor -o wide
你应该看到类似以下的输出:
NAMESPACE APP ID NAME INSTANCE ID CREATED LAST UPDATE STATUS
default order-processor OrderProcessingWorkflow e4d3807c 2025-11-07T12:29:37Z 2025-11-07T12:29:52Z COMPLETED
查看工作流的详细执行历史记录:
dapr workflow history e4d3807c --app-id order-processor
你应该看到类似以下的输出:
TYPE NAME EVENTID ELAPSED STATUS DETAILS
ExecutionStarted OrderProcessingWorkflow - Age:1.1m RUNNING orchestration start
OrchestratorStarted - - 13.4ms RUNNING replay cycle start
TaskScheduled NotifyActivity 0 1.3ms RUNNING activity=NotifyActivity
TaskCompleted - - 2.6ms RUNNING eventId=0
OrchestratorStarted - - 2.6ms RUNNING replay cycle start
TaskScheduled VerifyInventoryActivity 1 637.6µs RUNNING activity=VerifyInventoryActivity
TaskCompleted - - 2.4ms RUNNING eventId=1
OrchestratorStarted - - 1.7ms RUNNING replay cycle start
TaskScheduled ProcessPaymentActivity 2 439.3µs RUNNING activity=ProcessPaymentActivity
TaskCompleted - - 1.6ms RUNNING eventId=2
OrchestratorStarted - - 1.5ms RUNNING replay cycle start
TaskScheduled UpdateInventoryActivity 3 311.2µs RUNNING activity=UpdateInventoryActivity
TaskCompleted - - 2.4ms RUNNING eventId=3
OrchestratorStarted - - 2.7ms RUNNING replay cycle start
TaskScheduled NotifyActivity 4 354.1µs RUNNING activity=NotifyActivity
TaskCompleted - - 2.5ms RUNNING eventId=4
OrchestratorStarted - - 1.6ms RUNNING replay cycle start
ExecutionCompleted - 5 517.1µs COMPLETED execDuration=38.7ms
如果你的工作流正在等待 外部事件,你可以引发一个。
它接受一个格式为 <instance-id>/<event-name> 的参数。
dapr workflow raise-event e4d3807c/ApprovalEvent \
--app-id order-processor \
--input '{"paymentId": "pay-123", "amount": 100.00}'
# 暂停工作流
dapr workflow suspend e4d3807c \
--app-id order-processor \
--reason "Waiting for inventory"
# 准备好后恢复
dapr workflow resume e4d3807c \
--app-id order-processor \
--reason "Inventory received"
测试后,清除已完成的工作流。
执行清除操作需要应用程序中运行工作流客户端。 需要工作流客户端连接以保持工作流状态机的完整性并防止损坏。 以下错误表明工作流客户端未运行:
failed to purge orchestration state: rpc error: code = FailedPrecondition desc = failed to purge orchestration state: failed to lookup actor: api error: code = FailedPrecondition desc = did not find address for actor
# 清除特定工作流
dapr workflow purge e4d3807c --app-id order-processor
# 或清除所有已完成的工作流
dapr workflow purge --app-id order-processor
我们一直在努力改进快速入门示例,重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
在我们的 discord 频道 中加入讨论。
探索 Dapr 教程 >>
让我们来看看 Dapr 的状态管理构建块。在本快速入门中,您将使用 Redis 状态存储来保存、获取和删除状态,可以通过以下方式:

虽然此示例使用 Redis,但您可以将其替换为任何支持的状态存储。
在继续快速入门之前,选择您首选的语言特定的 Dapr SDK。
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/python/sdk/order-processor
安装依赖项:
pip3 install -r requirements.txt
使用多应用运行运行 order-processor 服务以及 Dapr 边车。
dapr run -f .
注意:由于 Windows 中未定义 Python3.exe,您可能需要在运行
dapr run -f .之前,将dapr.yaml文件中的python3更改为python。
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
with DaprClient() as client:
# 将状态保存到状态存储中
client.save_state(DAPR_STORE_NAME, orderId, str(order))
logging.info('Saving Order: %s', order)
# 从状态存储中获取状态
result = client.get_state(DAPR_STORE_NAME, orderId)
logging.info('Result after get: ' + str(result.data))
# 从状态存储中删除状态
client.delete_state(store_name=DAPR_STORE_NAME, key=orderId)
logging.info('Deleting Order: %s', order)
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
dapr.yaml 多应用运行模板文件当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../resources/
apps:
- appID: order-processor
appDirPath: ./order-processor/
command: ["python3" , "app.py"]
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 还会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/javascript/sdk/order-processor
安装依赖项:
npm install
运行 order-processor 服务以及 Dapr 边车。
dapr run -f .
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
const client = new DaprClient()
// 将状态保存到状态存储中
await client.state.save(DAPR_STATE_STORE_NAME, order)
console.log("Saving Order: ", order)
// 从状态存储中获取状态
const savedOrder = await client.state.get(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Getting Order: ", savedOrder)
// 从状态存储中删除状态
await client.state.delete(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Deleting Order: ", order)
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP == Saving Order: { orderId: 1 }
== APP == Saving Order: { orderId: 2 }
== APP == Saving Order: { orderId: 3 }
== APP == Saving Order: { orderId: 4 }
== APP == Saving Order: { orderId: 5 }
== APP == Getting Order: { orderId: 1 }
== APP == Deleting Order: { orderId: 1 }
== APP == Getting Order: { orderId: 2 }
== APP == Deleting Order: { orderId: 2 }
== APP == Getting Order: { orderId: 3 }
== APP == Deleting Order: { orderId: 3 }
== APP == Getting Order: { orderId: 4 }
== APP == Deleting Order: { orderId: 4 }
== APP == Getting Order: { orderId: 5 }
== APP == Deleting Order: { orderId: 5 }
dapr.yaml 多应用运行模板文件当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../resources/
apps:
- appID: order-processor
appDirPath: ./order-processor/
command: ["npm", "run", "start"]
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
注意:.NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。Dapr v1.16 及更高版本将仅支持 .NET 8 和 .NET 9。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/csharp/sdk/order-processor
安装依赖项:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run -f .
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
var client = new DaprClientBuilder().Build();
// 将状态保存到状态存储中
await client.SaveStateAsync(DAPR_STORE_NAME, orderId.ToString(), order.ToString());
Console.WriteLine("Saving Order: " + order);
// 从状态存储中获取状态
var result = await client.GetStateAsync<string>(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Getting Order: " + result);
// 从状态存储中删除状态
await client.DeleteStateAsync(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Deleting Order: " + order);
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == Saving Order: Order { orderId = 1 }
== APP == Getting Order: Order { orderId = 1 }
== APP == Deleting Order: Order { orderId = 1 }
== APP == Saving Order: Order { orderId = 2 }
== APP == Getting Order: Order { orderId = 2 }
== APP == Deleting Order: Order { orderId = 2 }
== APP == Saving Order: Order { orderId = 3 }
== APP == Getting Order: Order { orderId = 3 }
== APP == Deleting Order: Order { orderId = 3 }
== APP == Saving Order: Order { orderId = 4 }
== APP == Getting Order: Order { orderId = 4 }
== APP == Deleting Order: Order { orderId = 4 }
== APP == Saving Order: Order { orderId = 5 }
== APP == Getting Order: Order { orderId = 5 }
== APP == Deleting Order: Order { orderId = 5 }
dapr.yaml 多应用运行模板文件当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../../resources/
apps:
- appID: order-processor
appDirPath: ./order-processor/
command: ["dotnet", "run"]
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/java/sdk/order-processor
安装依赖项:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run -f .
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
try (DaprClient client = new DaprClientBuilder().build()) {
for (int i = 1; i <= 10; i++) {
int orderId = i;
Order order = new Order();
order.setOrderId(orderId);
// 将状态保存到状态存储中
client.saveState(DAPR_STATE_STORE, String.valueOf(orderId), order).block();
LOGGER.info("Saving Order: " + order.getOrderId());
// 从状态存储中获取状态
State<Order> response = client.getState(DAPR_STATE_STORE, String.valueOf(orderId), Order.class).block();
LOGGER.info("Getting Order: " + response.getValue().getOrderId());
// 从状态存储中删除状态
client.deleteState(DAPR_STATE_STORE, String.valueOf(orderId)).block();
LOGGER.info("Deleting Order: " + orderId);
TimeUnit.MILLISECONDS.sleep(1000);
}
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
dapr.yaml 多应用运行模板文件当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../resources/
apps:
- appID: order-processor
appDirPath: ./order-processor/
command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"]
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/go/sdk/order-processor
安装依赖项:
go build .
运行 order-processor 服务以及 Dapr 边车。
dapr run -f .
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
client, err := dapr.NewClient()
// 将状态保存到状态存储中
_ = client.SaveState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId), []byte(order))
log.Print("Saving Order: " + string(order))
// 从状态存储中获取状态
result, _ := client.GetState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
fmt.Println("Getting Order: " + string(result.Value))
// 从状态存储中删除状态
_ = client.DeleteState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
log.Print("Deleting Order: " + string(order))
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == dapr client initializing for: 127.0.0.1:53689
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":1}
== APP == Getting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":2}
== APP == Getting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":3}
== APP == Getting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":4}
== APP == Getting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":5}
== APP == Getting Order: {"orderId":5}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5}
dapr.yaml 多应用运行模板文件当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../resources/
apps:
- appID: order-processor
appDirPath: ./order-processor/
command: ["go", "run", "."]
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。在继续快速入门之前,选择您首选的语言特定的 Dapr SDK。
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/python/sdk/order-processor
安装依赖项:
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources/ -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
with DaprClient() as client:
# 将状态保存到状态存储中
client.save_state(DAPR_STORE_NAME, orderId, str(order))
logging.info('Saving Order: %s', order)
# 从状态存储中获取状态
result = client.get_state(DAPR_STORE_NAME, orderId)
logging.info('Result after get: ' + str(result.data))
# 从状态存储中删除状态
client.delete_state(store_name=DAPR_STORE_NAME, key=orderId)
logging.info('Deleting Order: %s', order)
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/javascript/sdk/order-processor
安装依赖项,其中包括 JavaScript SDK 中的 @dapr/dapr 包:
npm install
验证服务目录中包含以下文件:
package.jsonpackage-lock.json运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm run start
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
const client = new DaprClient()
// 将状态保存到状态存储中
await client.state.save(DAPR_STATE_STORE_NAME, order)
console.log("Saving Order: ", order)
// 从状态存储中获取状态
const savedOrder = await client.state.get(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Getting Order: ", savedOrder)
// 从状态存储中删除状态
await client.state.delete(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Deleting Order: ", order)
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP == Saving Order: { orderId: 1 }
== APP == Saving Order: { orderId: 2 }
== APP == Saving Order: { orderId: 3 }
== APP == Saving Order: { orderId: 4 }
== APP == Saving Order: { orderId: 5 }
== APP == Getting Order: { orderId: 1 }
== APP == Deleting Order: { orderId: 1 }
== APP == Getting Order: { orderId: 2 }
== APP == Deleting Order: { orderId: 2 }
== APP == Getting Order: { orderId: 3 }
== APP == Deleting Order: { orderId: 3 }
== APP == Getting Order: { orderId: 4 }
== APP == Deleting Order: { orderId: 4 }
== APP == Getting Order: { orderId: 5 }
== APP == Deleting Order: { orderId: 5 }
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/csharp/sdk/order-processor
恢复 NuGet 包:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
var client = new DaprClientBuilder().Build();
// 将状态保存到状态存储中
await client.SaveStateAsync(DAPR_STORE_NAME, orderId.ToString(), order.ToString());
Console.WriteLine("Saving Order: " + order);
// 从状态存储中获取状态
var result = await client.GetStateAsync<string>(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Getting Order: " + result);
// 从状态存储中删除状态
await client.DeleteStateAsync(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Deleting Order: " + order);
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == Saving Order: Order { orderId = 1 }
== APP == Getting Order: Order { orderId = 1 }
== APP == Deleting Order: Order { orderId = 1 }
== APP == Saving Order: Order { orderId = 2 }
== APP == Getting Order: Order { orderId = 2 }
== APP == Deleting Order: Order { orderId = 2 }
== APP == Saving Order: Order { orderId = 3 }
== APP == Getting Order: Order { orderId = 3 }
== APP == Deleting Order: Order { orderId = 3 }
== APP == Saving Order: Order { orderId = 4 }
== APP == Getting Order: Order { orderId = 4 }
== APP == Deleting Order: Order { orderId = 4 }
== APP == Saving Order: Order { orderId = 5 }
== APP == Getting Order: Order { orderId = 5 }
== APP == Deleting Order: Order { orderId = 5 }
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/java/sdk/order-processor
安装依赖项:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
try (DaprClient client = new DaprClientBuilder().build()) {
for (int i = 1; i <= 10; i++) {
int orderId = i;
Order order = new Order();
order.setOrderId(orderId);
// 将状态保存到状态存储中
client.saveState(DAPR_STATE_STORE, String.valueOf(orderId), order).block();
LOGGER.info("Saving Order: " + order.getOrderId());
// 从状态存储中获取状态
State<Order> response = client.getState(DAPR_STATE_STORE, String.valueOf(orderId), Order.class).block();
LOGGER.info("Getting Order: " + response.getValue().getOrderId());
// 从状态存储中删除状态
client.deleteState(DAPR_STATE_STORE, String.valueOf(orderId)).block();
LOGGER.info("Deleting Order: " + orderId);
TimeUnit.MILLISECONDS.sleep(1000);
}
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd state_management/go/sdk/order-processor
安装依赖项并构建应用程序:
go build .
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources -- go run .
order-processor 服务向在 statestore.yaml 组件中定义的 statestore 实例写入、读取和删除 orderId 键值对。服务启动后,它会执行循环。
client, err := dapr.NewClient()
// 将状态保存到状态存储中
_ = client.SaveState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId), []byte(order))
log.Print("Saving Order: " + string(order))
// 从状态存储中获取状态
result, _ := client.GetState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
fmt.Println("Getting Order: " + string(result.Value))
// 从状态存储中删除状态
_ = client.DeleteState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
log.Print("Deleting Order: " + string(order))
注意,如上面的代码所示,代码将应用程序状态保存在 Dapr 状态存储中,读取它,然后删除它。
Order-processor 输出:
== APP == dapr client initializing for: 127.0.0.1:53689
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":1}
== APP == Getting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":2}
== APP == Getting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":3}
== APP == Getting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":4}
== APP == Getting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":5}
== APP == Getting Order: {"orderId":5}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5}
statestore.yaml 组件文件当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在您的本地计算机上运行一个 Redis 容器,位于:
%UserProfile%\.dapr\components\statestore.yaml~/.dapr/components/statestore.yaml使用 statestore.yaml 组件,您可以轻松地替换状态存储,而无需更改代码。
此快速入门包含的 Redis statestore.yaml 文件包含以下内容:
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"
在 YAML 文件中:
metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。spec/metadata 定义与组件使用的 Redis 实例的连接。我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?
加入我们的 Discord 频道 参与讨论。
让我们来看一下 Dapr 的绑定构建块。使用绑定,您可以:
在本快速入门中,您使用输入 Cron 绑定安排一个批处理脚本每 10 秒运行一次。该脚本处理一个 JSON 文件,并使用 PostgreSQL Dapr 绑定将数据输出到 SQL 数据库。

在继续快速入门之前,请选择您偏好的特定语言的 Dapr SDK。
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在您的机器上的 Docker 容器中本地运行 PostgreSQL 实例。快速入门示例包含一个 Docker Compose 文件,用于在本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。
在终端窗口中,从 Quickstarts 克隆目录的根目录导航到 bindings/db 目录。
cd bindings/db
运行以下命令来设置容器:
docker compose up
验证容器是否在本地运行。
docker ps
输出应包括:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db
在新的终端窗口中,导航到 SDK 目录。
cd bindings/python/sdk/batch
安装依赖项:
pip3 install -r requirements.txt
与 Dapr 边车一起运行 batch-sdk 服务。
dapr run --app-id batch-sdk --app-port 50051 --resources-path ../../../components -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,您可能需要使用
python app.py而不是python3 app.py。
process_batch 函数内的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器在您的应用程序中寻找由 Dapr 边车通过 HTTP POST 调用的路由。
# 由 Dapr 输入绑定触发
@app.route('/' + cron_binding_name, methods=['POST'])
def process_batch():
batch-sdk 服务使用 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定,将 OrderId、Customer 和 Price 记录插入到 orders 表中。
with DaprClient() as d:
sqlCmd = ('insert into orders (orderid, customer, price) values ' +
'(%s, \'%s\', %s)' % (order_line['orderid'],
order_line['customer'],
order_line['price']))
payload = {'sql': sqlCmd}
print(sqlCmd, flush=True)
try:
# 使用 Dapr 输出绑定通过 HTTP Post 插入订单
resp = d.invoke_binding(binding_name=sql_binding, operation='exec',
binding_metadata=payload, data='')
return resp
except Exception as e:
print(e, flush=True)
raise SystemExit(e)
请注意,如上所述,代码使用 OrderId、Customer 和 Price 作为负载调用输出绑定。
您的输出绑定的 print 语句输出:
== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56)
== APP == Finished processing batch
在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。
cd bindings/db
运行以下命令以启动交互式 psql CLI:
docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password
在 admin=# 提示符下,切换到 orders 表:
\c orders;
在 orders=# 提示符下,选择所有行:
select * from orders;
输出应如下所示:
orderid | customer | price
---------+------------+--------
1 | John Smith | 100.32
2 | Jane Bond | 15.4
3 | Tony James | 35.56
components\binding-cron.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
batch)为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: cron
namespace: quickstarts
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "@every 10s" # valid cron schedule
- name: direction
value: "input" # direction of the cron binding
注意: binding-cron.yaml 的 metadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。
component\binding-postgresql.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL使用 binding-postgresql.yaml 组件,您可以轻松更换后端数据库 绑定,而无需更改代码。
为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sqldb
namespace: quickstarts
spec:
type: bindings.postgresql
version: v1
metadata:
- name: url # Required
value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
- name: direction
value: "output" # direction of the postgresql binding
在 YAML 文件中:
spec/type 指定此绑定使用 PostgreSQL。spec/metadata 定义组件使用的 PostgreSQL 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在您的机器上的 Docker 容器中本地运行 PostgreSQL 实例。快速入门示例包含一个 Docker Compose 文件,用于在本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。
在终端窗口中,从 Quickstarts 克隆目录的根目录导航到 bindings/db 目录。
cd bindings/db
运行以下命令来设置容器:
docker compose up
验证容器是否在本地运行。
docker ps
输出应包括:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db
在新的终端窗口中,导航到 SDK 目录。
cd bindings/javascript/sdk/batch
安装依赖项:
npm install
与 Dapr 边车一起运行 batch-sdk 服务。
dapr run --app-id batch-sdk --app-port 5002 --dapr-http-port 3500 --resources-path ../../../components -- node index.js
process_batch 函数内的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器在您的应用程序中寻找由 Dapr 边车通过 HTTP POST 调用的路由。
async function start() {
await server.binding.receive(cronBindingName,processBatch);
await server.start();
}
batch-sdk 服务使用 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定,将 OrderId、Customer 和 Price 记录插入到 orders 表中。
async function processBatch(){
const loc = '../../orders.json';
fs.readFile(loc, 'utf8', (err, data) => {
const orders = JSON.parse(data).orders;
orders.forEach(order => {
let sqlCmd = `insert into orders (orderid, customer, price) values (${order.orderid}, '${order.customer}', ${order.price});`;
let payload = `{ "sql": "${sqlCmd}" } `;
console.log(payload);
client.binding.send(postgresBindingName, "exec", "", JSON.parse(payload));
});
console.log('Finished processing batch');
});
return 0;
}
请注意,如上所述,代码使用 OrderId、Customer 和 Price 作为负载调用输出绑定。
您的输出绑定的 print 语句输出:
== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56)
在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。
cd bindings/db
运行以下命令以启动交互式 Postgres CLI:
docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password
在 admin=# 提示符下,切换到 orders 表:
\c orders;
在 orders=# 提示符下,选择所有行:
select * from orders;
输出应如下所示:
orderid | customer | price
---------+------------+--------
1 | John Smith | 100.32
2 | Jane Bond | 15.4
3 | Tony James | 35.56
components\binding-cron.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
batch)为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: cron
namespace: quickstarts
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "@every 10s" # valid cron schedule
- name: direction
value: "input" # direction of the cron binding
注意: binding-cron.yaml 的 metadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。
component\binding-postgresql.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL使用 binding-postgresql.yaml 组件,您可以轻松更换后端数据库 绑定,而无需更改代码。
为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sqldb
namespace: quickstarts
spec:
type: bindings.postgresql
version: v1
metadata:
- name: url # Required
value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
- name: direction
value: "output" # direction of the postgresql binding
在 YAML 文件中:
spec/type 指定此绑定使用 PostgreSQL。spec/metadata 定义组件使用的 PostgreSQL 实例的连接。对于此示例,您需要:
注意: .NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。在 Dapr v1.16 及更高版本中将仅支持 .NET 8 和 .NET 9。
git clone https://github.com/dapr/quickstarts.git
在您的机器上的 Docker 容器中本地运行 PostgreSQL 实例。快速入门示例包含一个 Docker Compose 文件,用于在本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。
在终端窗口中,从 Quickstarts 克隆目录的根目录导航到 bindings/db 目录。
cd bindings/db
运行以下命令来设置容器:
docker compose up
验证容器是否在本地运行。
docker ps
输出应包括:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db
在新的终端窗口中,导航到 SDK 目录。
cd bindings/csharp/sdk/batch
安装依赖项:
dotnet restore
dotnet build batch.csproj
与 Dapr 边车一起运行 batch-sdk 服务。
dapr run --app-id batch-sdk --app-port 7002 --resources-path ../../../components -- dotnet run
process_batch 函数内的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器在您的应用程序中寻找由 Dapr 边车通过 HTTP POST 调用的路由。
app.MapPost("/" + cronBindingName, async () => {
// ...
});
batch-sdk 服务使用 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定,将 OrderId、Customer 和 Price 记录插入到 orders 表中。
// ...
string jsonFile = File.ReadAllText("../../../orders.json");
var ordersArray = JsonSerializer.Deserialize<Orders>(jsonFile);
using var client = new DaprClientBuilder().Build();
foreach(Order ord in ordersArray?.orders ?? new Order[] {}){
var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});";
var command = new Dictionary<string,string>(){
{"sql",
sqlText}
};
// ...
}
// 使用 Dapr 输出绑定通过 Dapr Client SDK 插入订单
await client.InvokeBindingAsync(bindingName: sqlBindingName, operation: "exec", data: "", metadata: command);
请注意,如上所述,代码使用 OrderId、Customer 和 Price 作为负载调用输出绑定。
您的输出绑定的 print 语句输出:
== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);
== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);
== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);
== APP == Finished processing batch
在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。
cd bindings/db
运行以下命令以启动交互式 Postgres CLI:
docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password
在 admin=# 提示符下,切换到 orders 表:
\c orders;
在 orders=# 提示符下,选择所有行:
select * from orders;
输出应如下所示:
orderid | customer | price
---------+------------+--------
1 | John Smith | 100.32
2 | Jane Bond | 15.4
3 | Tony James | 35.56
components\binding-cron.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
batch)为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: cron
namespace: quickstarts
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "@every 10s" # valid cron schedule
- name: direction
value: "input" # direction of the cron binding
注意: binding-cron.yaml 的 metadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。
component\binding-postgresql.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL使用 binding-postgresql.yaml 组件,您可以轻松更换后端数据库 绑定,而无需更改代码。
为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sqldb
namespace: quickstarts
spec:
type: bindings.postgresql
version: v1
metadata:
- name: url # Required
value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
- name: direction
value: "output" # direction of the postgresql binding
在 YAML 文件中:
spec/type 指定此绑定使用 PostgreSQL。spec/metadata 定义组件使用的 PostgreSQL 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在您的机器上的 Docker 容器中本地运行 PostgreSQL 实例。快速入门示例包含一个 Docker Compose 文件,用于在本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。
在终端窗口中,从 Quickstarts 克隆目录的根目录导航到 bindings/db 目录。
cd bindings/db
运行以下命令来设置容器:
docker compose up
验证容器是否在本地运行。
docker ps
输出应包括:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db
在新的终端窗口中,导航到 SDK 目录。
cd bindings/java/sdk/batch
安装依赖项:
mvn clean install
与 Dapr 边车一起运行 batch-sdk 服务。
dapr run --app-id batch-sdk --app-port 8080 --resources-path ../../../components -- java -jar target/BatchProcessingService-0.0.1-SNAPSHOT.jar
process_batch 函数内的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器在您的应用程序中寻找由 Dapr 边车通过 HTTP POST 调用的路由。
@PostMapping(path = cronBindingPath, consumes = MediaType.ALL_VALUE)
public ResponseEntity<String> processBatch() throws IOException, Exception
batch-sdk 服务使用 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定,将 OrderId、Customer 和 Price 记录插入到 orders 表中。
try (DaprClient client = new DaprClientBuilder().build()) {
for (Order order : ordList.orders) {
String sqlText = String.format(
"insert into orders (orderid, customer, price) " +
"values (%s, '%s', %s);",
order.orderid, order.customer, order.price);
logger.info(sqlText);
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("sql", sqlText);
// 使用 Dapr SDK 调用 sql 输出绑定
client.invokeBinding(sqlBindingName, "exec", null, metadata).block();
}
logger.info("Finished processing batch");
return ResponseEntity.ok("Finished processing batch");
}
请注意,如上所述,代码使用 OrderId、Customer 和 Price 作为负载调用输出绑定。
您的输出绑定的 print 语句输出:
== APP == 2022-06-22 16:39:17.012 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Processing batch..
== APP == 2022-06-22 16:39:17.268 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);
== APP == 2022-06-22 16:39:17.838 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);
== APP == 2022-06-22 16:39:17.844 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);
== APP == 2022-06-22 16:39:17.848 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Finished processing batch
在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。
cd bindings/db
运行以下命令以启动交互式 Postgres CLI:
docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password
在 admin=# 提示符下,切换到 orders 表:
\c orders;
在 orders=# 提示符下,选择所有行:
select * from orders;
输出应如下所示:
orderid | customer | price
---------+------------+--------
1 | John Smith | 100.32
2 | Jane Bond | 15.4
3 | Tony James | 35.56
components\binding-cron.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
batch)为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: cron
namespace: quickstarts
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "@every 10s" # valid cron schedule
- name: direction
value: "input" # direction of the cron binding
注意: binding-cron.yaml 的 metadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。
component\binding-postgresql.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL使用 binding-postgresql.yaml 组件,您可以轻松更换后端数据库 绑定,而无需更改代码。
为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sqldb
namespace: quickstarts
spec:
type: bindings.postgresql
version: v1
metadata:
- name: url # Required
value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
- name: direction
value: "output" # direction of the postgresql binding
在 YAML 文件中:
spec/type 指定此绑定使用 PostgreSQL。spec/metadata 定义组件使用的 PostgreSQL 实例的连接。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
在您的机器上的 Docker 容器中本地运行 PostgreSQL 实例。快速入门示例包含一个 Docker Compose 文件,用于在本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。
在终端窗口中,从 Quickstarts 克隆目录的根目录导航到 bindings/db 目录。
cd bindings/db
运行以下命令来设置容器:
docker compose up
验证容器是否在本地运行。
docker ps
输出应包括:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db
在新的终端窗口中,导航到 SDK 目录。
cd bindings/go/sdk/batch
安装依赖项:
go build .
与 Dapr 边车一起运行 batch-sdk 服务。
dapr run --app-id batch-sdk --app-port 6002 --dapr-http-port 3502 --dapr-grpc-port 60002 --resources-path ../../../components -- go run .
process_batch 函数内的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器在您的应用程序中寻找由 Dapr 边车通过 HTTP POST 调用的路由。
// 由 Dapr 输入绑定触发
r.HandleFunc("/"+cronBindingName, processBatch).Methods("POST")
batch-sdk 服务使用 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定,将 OrderId、Customer 和 Price 记录插入到 orders 表中。
func sqlOutput(order Order) (err error) {
client, err := dapr.NewClient()
if err != nil {
return err
}
ctx := context.Background()
sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64))
fmt.Println(sqlCmd)
// 使用 Dapr 输出绑定通过 Dapr SDK 插入订单
in := &dapr.InvokeBindingRequest{
Name: sqlBindingName,
Operation: "exec",
Data: []byte(""),
Metadata: map[string]string{"sql": sqlCmd},
}
err = client.InvokeOutputBinding(ctx, in)
if err != nil {
return err
}
return nil
}
请注意,如上所述,代码使用 OrderId、Customer 和 Price 作为负载调用输出绑定。
您的输出绑定的 print 语句输出:
== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56)
在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。
cd bindings/db
运行以下命令以启动交互式 Postgres CLI:
docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password
在 admin=# 提示符下,切换到 orders 表:
\c orders;
在 orders=# 提示符下,选择所有行:
select * from orders;
输出应如下所示:
orderid | customer | price
---------+------------+--------
1 | John Smith | 100.32
2 | Jane Bond | 15.4
3 | Tony James | 35.56
components\binding-cron.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
batch)为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: cron
namespace: quickstarts
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "@every 10s" # valid cron schedule
- name: direction
value: "input" # direction of the cron binding
注意: binding-cron.yaml 的 metadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。
component\binding-postgresql.yaml 组件文件当您执行 dapr run 命令并指定组件路径时,Dapr 边车:
binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL使用 binding-postgresql.yaml 组件,您可以轻松更换后端数据库 绑定,而无需更改代码。
为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sqldb
namespace: quickstarts
spec:
type: bindings.postgresql
version: v1
metadata:
- name: url # Required
value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
- name: direction
value: "output" # direction of the postgresql binding
在 YAML 文件中:
spec/type 指定此绑定使用 PostgreSQL。spec/metadata 定义组件使用的 PostgreSQL 实例的连接。我们正在不断改进快速入门示例,重视您的反馈。您是否觉得此快速入门有帮助?您有改进建议吗?
在我们的 discord 频道 中加入讨论。
让我们来看看 Dapr 的 Actors 构建块。在此快速入门中,你将运行一个智能设备微服务和一个简单的控制台客户端,以演示 Dapr Actors 中的有状态对象模式。
目前,你可以使用 .NET SDK 体验此 actors 快速入门。
作为 .NET actors 快速入门的简要概述:
SmartDevice.Service 微服务,你托管:SmokeDetectorActor 烟雾报警对象ControllerActor 对象,用于命令和控制智能设备SmartDevice.Client 控制台应用,客户端应用与每个 actor 或控制器交互,以总体执行操作。SmartDevice.Interfaces 包含服务应用和客户端应用使用的共享接口和数据类型。
对于此示例,你需要:
注意: .NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。Dapr v1.16 及更高版本将仅支持 .NET 8 和 .NET 9。
git clone https://github.com/dapr/quickstarts.git
在新的终端窗口中,导航到 actors/csharp/sdk/service 目录并还原依赖项:
cd actors/csharp/sdk/service
dotnet build
运行 SmartDevice.Service,它将启动服务本身和 Dapr 边车:
dapr run --app-id actorservice --app-port 5001 --dapr-http-port 3500 --resources-path ../../../resources -- dotnet run --urls=http://localhost:5001/
预期输出:
== APP == info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
== APP == Request starting HTTP/1.1 GET http://127.0.0.1:5001/healthz - -
== APP == info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
== APP == Executing endpoint 'Dapr Actors Health Check'
== APP == info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
== APP == Executed endpoint 'Dapr Actors Health Check'
== APP == info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
== APP == Request finished HTTP/1.1 GET http://127.0.0.1:5001/healthz - - - 200 - text/plain 5.2599ms
在新的终端实例中,导航到 actors/csharp/sdk/client 目录并安装依赖项:
cd ./actors/csharp/sdk/client
dotnet build
运行 SmartDevice.Client 应用:
dapr run --app-id actorclient -- dotnet run
预期输出:
== APP == Startup up...
== APP == Calling SetDataAsync on SmokeDetectorActor:1...
== APP == Got response: Success
== APP == Calling GetDataAsync on SmokeDetectorActor:1...
== APP == Device 1 state: Location: First Floor, Status: Ready
== APP == Calling SetDataAsync on SmokeDetectorActor:2...
== APP == Got response: Success
== APP == Calling GetDataAsync on SmokeDetectorActor:2...
== APP == Device 2 state: Location: Second Floor, Status: Ready
== APP == Registering the IDs of both Devices...
== APP == Registered devices: 1, 2
== APP == Detecting smoke on Device 1...
== APP == Device 1 state: Location: First Floor, Status: Alarm
== APP == Device 2 state: Location: Second Floor, Status: Alarm
== APP == Sleeping for 16 seconds before checking status again to see reminders fire and clear alarms
== APP == Device 1 state: Location: First Floor, Status: Ready
== APP == Device 2 state: Location: Second Floor, Status: Ready
如果你在本地计算机上为 Dapr 配置了 Zipkin,则可以在 Zipkin Web UI(通常位于 http://localhost:9411/zipkin/)中查看 actor 与客户端的交互。

当你运行客户端应用时,发生了几件事:
两个 SmokeDetectorActor actors 在客户端应用程序中创建并使用对象状态进行初始化:
ActorProxy.Create<ISmartDevice>(actorId, actorType)proxySmartDevice.SetDataAsync(data)这些对象是可重入的并保存状态,如 proxySmartDevice.GetDataAsync() 所示。
// Actor Ids and types
var deviceId1 = "1";
var deviceId2 = "2";
var smokeDetectorActorType = "SmokeDetectorActor";
var controllerActorType = "ControllerActor";
Console.WriteLine("Startup up...");
// An ActorId uniquely identifies the first actor instance for the first device
var deviceActorId1 = new ActorId(deviceId1);
// Create a new instance of the data class that will be stored in the first actor
var deviceData1 = new SmartDeviceData(){
Location = "First Floor",
Status = "Ready",
};
// Create the local proxy by using the same interface that the service implements.
var proxySmartDevice1 = ActorProxy.Create<ISmartDevice>(deviceActorId1, smokeDetectorActorType);
// Now you can use the actor interface to call the actor's methods.
Console.WriteLine($"Calling SetDataAsync on {smokeDetectorActorType}:{deviceActorId1}...");
var setDataResponse1 = await proxySmartDevice1.SetDataAsync(deviceData1);
Console.WriteLine($"Got response: {setDataResponse1}");
Console.WriteLine($"Calling GetDataAsync on {smokeDetectorActorType}:{deviceActorId1}...");
var storedDeviceData1 = await proxySmartDevice1.GetDataAsync();
Console.WriteLine($"Device 1 state: {storedDeviceData1}");
// Create a second actor for second device
var deviceActorId2 = new ActorId(deviceId2);
// Create a new instance of the data class that will be stored in the first actor
var deviceData2 = new SmartDeviceData(){
Location = "Second Floor",
Status = "Ready",
};
// Create the local proxy by using the same interface that the service implements.
var proxySmartDevice2 = ActorProxy.Create<ISmartDevice>(deviceActorId2, smokeDetectorActorType);
// Now you can use the actor interface to call the second actor's methods.
Console.WriteLine($"Calling SetDataAsync on {smokeDetectorActorType}:{deviceActorId2}...");
var setDataResponse2 = await proxySmartDevice2.SetDataAsync(deviceData2);
Console.WriteLine($"Got response: {setDataResponse2}");
Console.WriteLine($"Calling GetDataAsync on {smokeDetectorActorType}:{deviceActorId2}...");
var storedDeviceData2 = await proxySmartDevice2.GetDataAsync();
Console.WriteLine($"Device 2 state: {storedDeviceData2}");
调用 SmokeDetectorActor 1 的 DetectSmokeAsync 方法。
public async Task DetectSmokeAsync()
{
var controllerActorId = new ActorId("controller");
var controllerActorType = "ControllerActor";
var controllerProxy = ProxyFactory.CreateActorProxy<IController>(controllerActorId, controllerActorType);
await controllerProxy.TriggerAlarmForAllDetectors();
}
调用 ControllerActor 的 TriggerAlarmForAllDetectors 方法。ControllerActor 在检测到烟雾时会在内部触发所有警报。
public async Task TriggerAlarmForAllDetectors()
{
var deviceIds = await ListRegisteredDeviceIdsAsync();
foreach (var deviceId in deviceIds)
{
var actorId = new ActorId(deviceId);
var proxySmartDevice = ProxyFactory.CreateActorProxy<ISmartDevice>(actorId, "SmokeDetectorActor");
await proxySmartDevice.SoundAlarm();
}
// Register a reminder to refresh and clear alarm state every 15 seconds
await this.RegisterReminderAsync("AlarmRefreshReminder", null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
}
控制台打印一条消息,指示已检测到烟雾。
// Smoke is detected on device 1 that triggers an alarm on all devices.
Console.WriteLine($"Detecting smoke on Device 1...");
proxySmartDevice1 = ActorProxy.Create<ISmartDevice>(deviceActorId1, smokeDetectorActorType);
await proxySmartDevice1.DetectSmokeAsync();
调用 SmokeDetectorActor 1 和 2 的 SoundAlarm 方法。
storedDeviceData1 = await proxySmartDevice1.GetDataAsync();
Console.WriteLine($"Device 1 state: {storedDeviceData1}");
storedDeviceData2 = await proxySmartDevice2.GetDataAsync();
Console.WriteLine($"Device 2 state: {storedDeviceData2}");
ControllerActor 还会使用 RegisterReminderAsync 创建一个持久化提醒,在 15 秒后调用 ClearAlarm。
// Register a reminder to refresh and clear alarm state every 15 seconds
await this.RegisterReminderAsync("AlarmRefreshReminder", null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
有关示例的完整上下文,请查看以下代码:
SmokeDetectorActor.cs:实现智能设备 actorsControllerActor.cs:实现管理所有设备的控制器 actorISmartDevice:每个 SmokeDetectorActor 的方法定义和共享数据类型IController:ControllerActor 的方法定义和共享数据类型我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
加入我们的 discord 频道 参与讨论。
了解有关 Actor 构建块 的更多信息
探索 Dapr 教程 >>Dapr 提供了一个专门的机密 API,允许开发者从机密存储中检索机密。在本快速入门中,你将:

在继续快速入门之前,请选择你喜欢的语言特定的 Dapr SDK。
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd secrets_management/python/sdk/order-processor
安装依赖项:
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,你可能需要使用
python app.py而不是python3 app.py。
order-processor 服务
请注意下面的 order-processor 服务如何指向:
local-secret-store.yaml 组件中定义的 DAPR_SECRET_STORE。secrets.json 中定义的机密。# app.py
DAPR_SECRET_STORE = 'localsecretstore'
SECRET_NAME = 'secret'
with DaprClient() as client:
secret = client.get_secret(store_name=DAPR_SECRET_STORE, key=SECRET_NAME)
logging.info('Fetched Secret: %s', secret.secret)
local-secret-store.yaml 组件
DAPR_SECRET_STORE 在 local-secret-store.yaml 组件文件中定义,位于 secrets_management/components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localsecretstore
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: secrets.json
- name: nestedSeparator
value: ":"
在 YAML 文件中:
metadata/name 是你的应用程序引用组件的方式(在代码示例中称为 DAPR_SECRET_STORE)。spec/metadata 定义组件使用的机密的连接。secrets.json 文件
SECRET_NAME 在 secrets.json 文件中定义,位于 secrets_management/python/sdk/order-processor:
{
"secret": "YourPasskeyHere"
}
如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。
Order-processor 输出:
== APP == INFO:root:Fetched Secret: {'secret': 'YourPasskeyHere'}
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd secrets_management/javascript/sdk/order-processor
安装依赖项:
npm install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ -- npm start
order-processor 服务
请注意下面的 order-processor 服务如何指向:
local-secret-store.yaml 组件中定义的 DAPR_SECRET_STORE。secrets.json 中定义的机密。// index.js
const DAPR_SECRET_STORE = "localsecretstore";
const SECRET_NAME = "secret";
async function main() {
// ...
const secret = await client.secret.get(DAPR_SECRET_STORE, SECRET_NAME);
console.log("Fetched Secret: " + JSON.stringify(secret));
}
local-secret-store.yaml 组件
DAPR_SECRET_STORE 在 local-secret-store.yaml 组件文件中定义,位于 secrets_management/components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localsecretstore
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: secrets.json
- name: nestedSeparator
value: ":"
在 YAML 文件中:
metadata/name 是你的应用程序引用组件的方式(在代码示例中称为 DAPR_SECRET_STORE)。spec/metadata 定义组件使用的机密的连接。secrets.json 文件
SECRET_NAME 在 secrets.json 文件中定义,位于 secrets_management/javascript/sdk/order-processor:
{
"secret": "YourPasskeyHere"
}
如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。
Order-processor 输出:
== APP ==
== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP ==
== APP == Fetched Secret: {"secret":"YourPasskeyHere"}
对于本示例,你需要:
注意:.NET 6 是本版本中 Dapr .NET SDK 包支持的最低 .NET 版本。在 Dapr v1.16 及更高版本中将仅支持 .NET 8 和 .NET 9。
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd secrets_management/csharp/sdk/order-processor
安装依赖项:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ -- dotnet run
order-processor 服务
请注意下面的 order-processor 服务如何指向:
local-secret-store.yaml 组件中定义的 DAPR_SECRET_STORE。secrets.json 中定义的机密。// Program.cs
const string DAPR_SECRET_STORE = "localsecretstore";
const string SECRET_NAME = "secret";
var client = new DaprClientBuilder().Build();
var secret = await client.GetSecretAsync(DAPR_SECRET_STORE, SECRET_NAME);
var secretValue = string.Join(", ", secret);
Console.WriteLine($"Fetched Secret: {secretValue}");
local-secret-store.yaml 组件
DAPR_SECRET_STORE 在 local-secret-store.yaml 组件文件中定义,位于 secrets_management/components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localsecretstore
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: secrets.json
- name: nestedSeparator
value: ":"
在 YAML 文件中:
metadata/name 是你的应用程序引用组件的方式(在代码示例中称为 DAPR_SECRET_NAME)。spec/metadata 定义组件使用的机密的连接。secrets.json 文件
SECRET_NAME 在 secrets.json 文件中定义,位于 secrets_management/csharp/sdk/order-processor:
{
"secret": "YourPasskeyHere"
}
如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。
Order-processor 输出:
== APP == Fetched Secret: [secret, YourPasskeyHere]
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd secrets_management/java/sdk/order-processor
安装依赖项:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
order-processor 服务
请注意下面的 order-processor 服务如何指向:
local-secret-store.yaml 组件中定义的 DAPR_SECRET_STORE。secrets.json 中定义的机密。// OrderProcessingServiceApplication.java
private static final String SECRET_STORE_NAME = "localsecretstore";
// ...
Map<String, String> secret = client.getSecret(SECRET_STORE_NAME, "secret").block();
System.out.println("Fetched Secret: " + secret);
local-secret-store.yaml 组件
DAPR_SECRET_STORE 在 local-secret-store.yaml 组件文件中定义,位于 secrets_management/components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localsecretstore
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: secrets.json
- name: nestedSeparator
value: ":"
在 YAML 文件中:
metadata/name 是你的应用程序引用组件的方式(在代码示例中称为 DAPR_SECRET_NAME)。spec/metadata 定义组件使用的机密的连接。secrets.json 文件
SECRET_NAME 在 secrets.json 文件中定义,位于 secrets_management/java/sdk/order-processor:
{
"secret": "YourPasskeyHere"
}
如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。
Order-processor 输出:
== APP == Fetched Secret: {secret=YourPasskeyHere}
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd secrets_management/go/sdk/order-processor
安装依赖项:
go build .
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ -- go run .
order-processor 服务
请注意下面的 order-processor 服务如何指向:
local-secret-store.yaml 组件中定义的 DAPR_SECRET_STORE。secrets.json 中定义的机密。const DAPR_SECRET_STORE = "localsecretstore"
const SECRET_NAME = "secret"
// ...
secret, err := client.GetSecret(ctx, DAPR_SECRET_STORE, SECRET_NAME, nil)
if secret != nil {
fmt.Println("Fetched Secret: ", secret[SECRET_NAME])
}
local-secret-store.yaml 组件
DAPR_SECRET_STORE 在 local-secret-store.yaml 组件文件中定义,位于 secrets_management/components:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localsecretstore
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: secrets.json
- name: nestedSeparator
value: ":"
在 YAML 文件中:
metadata/name 是你的应用程序引用组件的方式(在代码示例中称为 DAPR_SECRET_NAME)。spec/metadata 定义组件使用的机密的连接。secrets.json 文件
SECRET_NAME 在 secrets.json 文件中定义,位于 secrets_management/go/sdk/order-processor:
{
"secret": "YourPasskeyHere"
}
如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。
Order-processor 输出:
== APP == Fetched Secret: YourPasskeyHere
我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
加入我们的 discord 频道 进行讨论。
让我们来看一下 Dapr 的 配置构建块。配置项通常具有动态特性,与消费它的应用程序需求紧密耦合。配置项是包含配置数据的键/值对,例如:
在本快速入门中,你将运行一个使用配置 API 的 order-processor 微服务。该服务:

在继续进行快速入门之前,选择你首选的语言特定的 Dapr SDK。
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1 和 orderId2 设置值。
docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"
order-processor 服务从 Quickstarts 克隆目录的根目录导航到 order-processor 目录。
cd configuration/python/sdk/order-processor
安装依赖项:
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6001 -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,你可能需要使用
python app.py而不是python3 app.py。
预期输出:
== APP == Configuration for orderId1 : value: "101"
== APP ==
== APP == Configuration for orderId2 : value: "102"
== APP ==
== APP == App unsubscribed from config changes
应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1 和 orderId2 的值:
docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"
再次运行 order-processor 服务:
dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6001 -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,你可能需要使用
python app.py而不是python3 app.py。
应用将返回更新后的配置值:
== APP == Configuration for orderId1 : value: "103"
== APP ==
== APP == Configuration for orderId2 : value: "104"
== APP ==
order-processor 服务order-processor 服务包含以下代码:
获取配置项:
# Get config items from the config store
for config_item in CONFIGURATION_ITEMS:
config = client.get_configuration(store_name=DAPR_CONFIGURATION_STORE, keys=[config_item], config_metadata={})
print(f"Configuration for {config_item} : {config.items[config_item]}", flush=True)
订阅配置更新:
# Subscribe for configuration changes
configuration = await client.subscribe_configuration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS)
取消订阅配置更新并退出应用:
# Unsubscribe from configuration updates
unsubscribed = True
for config_item in CONFIGURATION_ITEMS:
unsub_item = client.unsubscribe_configuration(DAPR_CONFIGURATION_STORE, config_item)
#...
if unsubscribed == True:
print("App unsubscribed from config changes", flush=True)
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1 和 orderId2 设置值。
docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"
order-processor 服务从 Quickstarts 克隆目录的根目录导航到 order-processor 目录。
cd configuration/javascript/sdk/order-processor
安装依赖项:
npm install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components/ --app-protocol grpc --dapr-grpc-port 3500 -- node index.js
预期输出:
== APP == Configuration for orderId1: {"key":"orderId1","value":"101","version":"","metadata":{}}
== APP == Configuration for orderId2: {"key":"orderId2","value":"102","version":"","metadata":{}}
== APP == App unsubscribed to config changes
应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1 和 orderId2 的值:
docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"
再次运行 order-processor 服务:
dapr run --app-id order-processor --resources-path ../../../components/ --app-protocol grpc --dapr-grpc-port 3500 -- node index.js
应用将返回更新后的配置值:
== APP == Configuration for orderId1: {"key":"orderId1","value":"103","version":"","metadata":{}}
== APP == Configuration for orderId2: {"key":"orderId2","value":"104","version":"","metadata":{}}
order-processor 服务order-processor 服务包含以下代码:
获取配置项:
// Get config items from the config store
//...
const config = await client.configuration.get(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);
Object.keys(config.items).forEach((key) => {
console.log("Configuration for " + key + ":", JSON.stringify(config.items[key]));
});
订阅配置更新:
// Subscribe to config updates
try {
const stream = await client.configuration.subscribeWithKeys(
DAPR_CONFIGURATION_STORE,
CONFIGURATION_ITEMS,
(config) => {
console.log("Configuration update", JSON.stringify(config.items));
}
);
取消订阅配置更新并退出应用:
// Unsubscribe to config updates and exit app after 20 seconds
setTimeout(() => {
stream.stop();
console.log("App unsubscribed to config changes");
process.exit(0);
},
对于本示例,你需要:
注意:.NET 6 是本版本中 Dapr .NET SDK 包支持的最低 .NET 版本。在 Dapr v1.16 及更高版本中将仅支持 .NET 8 和 .NET 9。
git clone https://github.com/dapr/quickstarts.git
克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1 和 orderId2 设置值。
docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"
order-processor 服务从 Quickstarts 克隆目录的根目录导航到 order-processor 目录。
cd configuration/csharp/sdk/order-processor
恢复 NuGet 包:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor-http --resources-path ../../../components/ --app-port 7001 -- dotnet run --project .
预期输出:
== APP == Configuration for orderId1: {"Value":"101","Version":"","Metadata":{}}
== APP == Configuration for orderId2: {"Value":"102","Version":"","Metadata":{}}
== APP == App unsubscribed from config changes
应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1 和 orderId2 的值:
docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"
再次运行 order-processor 服务:
dapr run --app-id order-processor-http --resources-path ../../../components/ --app-port 7001 -- dotnet run --project .
应用将返回更新后的配置值:
== APP == Configuration for orderId1: {"Value":"103","Version":"","Metadata":{}}
== APP == Configuration for orderId2: {"Value":"104","Version":"","Metadata":{}}
order-processor 服务order-processor 服务包含以下代码:
获取配置项:
// Get config from configuration store
GetConfigurationResponse config = await client.GetConfiguration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);
foreach (var item in config.Items)
{
var cfg = System.Text.Json.JsonSerializer.Serialize(item.Value);
Console.WriteLine("Configuration for " + item.Key + ": " + cfg);
}
订阅配置更新:
// Subscribe to config updates
SubscribeConfigurationResponse subscribe = await client.SubscribeConfiguration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);
取消订阅配置更新并退出应用:
// Unsubscribe to config updates and exit the app
try
{
client.UnsubscribeConfiguration(DAPR_CONFIGURATION_STORE, subscriptionId);
Console.WriteLine("App unsubscribed from config changes");
Environment.Exit(0);
}
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1 和 orderId2 设置值。
docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"
order-processor 服务从 Quickstarts 克隆目录的根目录导航到 order-processor 目录。
cd configuration/java/sdk/order-processor
安装依赖项:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
预期输出:
== APP == Configuration for orderId1: {'value':'101'}
== APP == Configuration for orderId2: {'value':'102'}
== APP == App unsubscribed to config changes
应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1 和 orderId2 的值:
docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"
再次运行 order-processor 服务:
dapr run --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
应用将返回更新后的配置值:
== APP == Configuration for orderId1: {'value':'103'}
== APP == Configuration for orderId2: {'value':'104'}
order-processor 服务order-processor 服务包含以下代码:
获取配置项:
// Get config items from the config store
try (DaprPreviewClient client = (new DaprClientBuilder()).buildPreviewClient()) {
for (String configurationItem : CONFIGURATION_ITEMS) {
ConfigurationItem item = client.getConfiguration(DAPR_CONFIGURATON_STORE, configurationItem).block();
System.out.println("Configuration for " + configurationItem + ": {'value':'" + item.getValue() + "'}");
}
订阅配置更新:
// Subscribe for config changes
Flux<SubscribeConfigurationResponse> subscription = client.subscribeConfiguration(DAPR_CONFIGURATON_STORE,
CONFIGURATION_ITEMS.toArray(String[]::new));
取消订阅配置更新并退出应用:
// Unsubscribe from config changes
UnsubscribeConfigurationResponse unsubscribe = client
.unsubscribeConfiguration(subscriptionId, DAPR_CONFIGURATON_STORE).block();
if (unsubscribe.getIsUnsubscribed()) {
System.out.println("App unsubscribed to config changes");
}
对于本示例,你需要:
git clone https://github.com/dapr/quickstarts.git
克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1 和 orderId2 设置值。
docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"
order-processor 服务从 Quickstarts 克隆目录的根目录导航到 order-processor 目录。
cd configuration/go/sdk/order-processor
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --app-port 6001 --resources-path ../../../components -- go run .
预期输出:
== APP == Configuration for orderId1: {"Value":"101","Version":"","Metadata":null}
== APP == Configuration for orderId2: {"Value":"102","Version":"","Metadata":null}
== APP == dapr configuration subscribe finished.
== APP == App unsubscribed to config changes
应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1 和 orderId2 的值:
docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"
再次运行 order-processor 服务:
dapr run --app-id order-processor --app-port 6001 --resources-path ../../../components -- go run .
应用将返回更新后的配置值:
== APP == Configuration for orderId1: {"Value":"103","Version":"","Metadata":null}
== APP == Configuration for orderId2: {"Value":"104","Version":"","Metadata":null}
order-processor 服务order-processor 服务包含以下代码:
获取配置项:
// Get config items from config store
for _, item := range CONFIGURATION_ITEMS {
config, err := client.GetConfigurationItem(ctx, DAPR_CONFIGURATION_STORE, item)
//...
c, _ := json.Marshal(config)
fmt.Println("Configuration for " + item + ": " + string(c))
}
订阅配置更新:
// Subscribe for config changes
err = client.SubscribeConfigurationItems(ctx, DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS, func(id string, config map[string]*dapr.ConfigurationItem) {
// First invocation when app subscribes to config changes only returns subscription id
if len(config) == 0 {
fmt.Println("App subscribed to config changes with subscription id: " + id)
subscriptionId = id
return
}
})
取消订阅配置更新并退出应用:
// Unsubscribe to config updates and exit app after 20 seconds
select {
case <-ctx.Done():
err = client.UnsubscribeConfigurationItems(context.Background(), DAPR_CONFIGURATION_STORE, subscriptionId)
//...
{
fmt.Println("App unsubscribed to config changes")
}
观看此视频演示配置 API 快速入门:
我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
在我们的 discord 频道 中加入讨论。
让我们来看看 Dapr 密码学构建块。在这个快速入门中,你将创建一个使用 Dapr 密码学 API 加密和解数据的应用程序。你将:

目前,你可以使用 Go SDK 体验密码学 API。
此快速入门包含一个名为
crypto-quickstart的 JavaScript 应用程序。
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端中,从根目录导航到密码学示例。
cd cryptography/javascript/sdk
导航到包含源代码的文件夹:
cd ./crypto-quickstart
安装依赖项:
npm install
应用程序代码定义了两个必需的密钥:
使用 OpenSSL 生成两个密钥,一个 RSA 密钥和一个 AES 密钥,并将它们写入两个文件:
mkdir -p keys
# 生成私有 RSA 密钥,4096 位密钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
# 为 AES 生成 256 位密钥
openssl rand -out keys/symmetric-key-256 32
使用 Dapr 运行 Go 服务应用程序:
dapr run --app-id crypto-quickstart --resources-path ../../../components/ -- npm start
预期输出
== APP == 2023-10-25T14:30:50.435Z INFO [GRPCClient, GRPCClient] Opening connection to 127.0.0.1:58173
== APP == == Encrypting message using buffers
== APP == Encrypted the message, got 856 bytes
== APP == == Decrypting message using buffers
== APP == Decrypted the message, got 24 bytes
== APP == The secret is "passw0rd"
== APP == == Encrypting message using streams
== APP == Encrypting federico-di-dio-photography-Q4g0Q-eVVEg-unsplash.jpg to encrypted.out
== APP == Encrypted the message to encrypted.out
== APP == == Decrypting message using streams
== APP == Decrypting encrypted.out to decrypted.out.jpg
== APP == Decrypted the message to decrypted.out.jpg
local-storage.yaml早些时候,你在 crypto-quickstarts 内创建了一个名为 keys 的目录。在 local-storage 组件 YAML 中,path 元数据映射到新创建的 keys 目录。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localstorage
spec:
type: crypto.dapr.localstorage
version: v1
metadata:
- name: path
# Path is relative to the folder where the example is located
value: ./keys
index.mjs应用程序文件 使用你生成的 RSA 和 AES 密钥加密和解密消息和文件。应用程序创建一个新的 Dapr SDK 客户端:
async function start() {
const client = new DaprClient({
daprHost,
daprPort,
communicationProtocol: CommunicationProtocolEnum.GRPC,
});
// Encrypt and decrypt a message from a buffer
await encryptDecryptBuffer(client);
// Encrypt and decrypt a message using streams
await encryptDecryptStream(client);
}
创建客户端后,应用程序加密一条消息:
async function encryptDecryptBuffer(client) {
// Message to encrypt
const plaintext = `The secret is "passw0rd"`
// First, encrypt the message
console.log("== Encrypting message using buffers");
const encrypted = await client.crypto.encrypt(plaintext, {
componentName: "localstorage",
keyName: "rsa-private-key.pem",
keyWrapAlgorithm: "RSA",
});
console.log("Encrypted the message, got", encrypted.length, "bytes");
然后应用程序解密消息:
// Decrypt the message
console.log("== Decrypting message using buffers");
const decrypted = await client.crypto.decrypt(encrypted, {
componentName: "localstorage",
});
console.log("Decrypted the message, got", decrypted.length, "bytes");
console.log(decrypted.toString("utf8"));
// ...
}
接下来,应用程序加密一个大的图像文件:
async function encryptDecryptStream(client) {
// First, encrypt the message
console.log("== Encrypting message using streams");
console.log("Encrypting", testFileName, "to encrypted.out");
await pipeline(
createReadStream(testFileName),
await client.crypto.encrypt({
componentName: "localstorage",
keyName: "symmetric-key-256",
keyWrapAlgorithm: "A256KW",
}),
createWriteStream("encrypted.out"),
);
console.log("Encrypted the message to encrypted.out");
然后应用程序解密大的图像文件:
// Decrypt the message
console.log("== Decrypting message using streams");
console.log("Decrypting encrypted.out to decrypted.out.jpg");
await pipeline(
createReadStream("encrypted.out"),
await client.crypto.decrypt({
componentName: "localstorage",
}),
createWriteStream("decrypted.out.jpg"),
);
console.log("Decrypted the message to decrypted.out.jpg");
}
此快速入门包含一个名为
crypto-quickstart的 Go 应用程序。
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端中,从根目录导航到密码学示例。
cd cryptography/go/sdk
导航到包含源代码的文件夹:
cd ./crypto-quickstart
应用程序代码定义了两个必需的密钥:
使用 OpenSSL 生成两个密钥,一个 RSA 密钥和一个 AES 密钥,并将它们写入两个文件:
mkdir -p keys
# 生成私有 RSA 密钥,4096 位密钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
# 为 AES 生成 256 位密钥
openssl rand -out keys/symmetric-key-256 32
使用 Dapr 运行 Go 服务应用程序:
dapr run --app-id crypto-quickstart --resources-path ../../../components/ -- go run .
预期输出
== APP == dapr client initializing for: 127.0.0.1:52407
== APP == Encrypted the message, got 856 bytes
== APP == Decrypted the message, got 24 bytes
== APP == The secret is "passw0rd"
== APP == Wrote decrypted data to encrypted.out
== APP == Wrote decrypted data to decrypted.out.jpg
local-storage.yaml早些时候,你在 crypto-quickstarts 内创建了一个名为 keys 的目录。在 local-storage 组件 YAML 中,path 元数据映射到新创建的 keys 目录。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localstorage
spec:
type: crypto.dapr.localstorage
version: v1
metadata:
- name: path
# Path is relative to the folder where the example is located
value: ./keys
app.go应用程序文件 使用你生成的 RSA 和 AES 密钥加密和解密消息和文件。应用程序创建一个新的 Dapr SDK 客户端:
func main() {
// Create a new Dapr SDK client
client, err := dapr.NewClient()
//...
// Step 1: encrypt a string using the RSA key, then decrypt it and show the output in the terminal
encryptDecryptString(client)
// Step 2: encrypt a large file and then decrypt it, using the AES key
encryptDecryptFile(client)
}
创建客户端后,应用程序加密一条消息:
func encryptDecryptString(client dapr.Client) {
// ...
// Encrypt the message
encStream, err := client.Encrypt(context.Background(),
strings.NewReader(message),
dapr.EncryptOptions{
ComponentName: CryptoComponentName,
// Name of the key to use
// Since this is a RSA key, we specify that as key wrapping algorithm
KeyName: RSAKeyName,
KeyWrapAlgorithm: "RSA",
},
)
// ...
// The method returns a readable stream, which we read in full in memory
encBytes, err := io.ReadAll(encStream)
// ...
fmt.Printf("Encrypted the message, got %d bytes\n", len(encBytes))
然后应用程序解密消息:
// Now, decrypt the encrypted data
decStream, err := client.Decrypt(context.Background(),
bytes.NewReader(encBytes),
dapr.DecryptOptions{
// We just need to pass the name of the component
ComponentName: CryptoComponentName,
// Passing the name of the key is optional
KeyName: RSAKeyName,
},
)
// ...
// The method returns a readable stream, which we read in full in memory
decBytes, err := io.ReadAll(decStream)
// ...
// Print the message on the console
fmt.Printf("Decrypted the message, got %d bytes\n", len(decBytes))
fmt.Println(string(decBytes))
}
接下来,应用程序加密一个大的图像文件:
func encryptDecryptFile(client dapr.Client) {
const fileName = "liuguangxi-66ouBTTs_x0-unsplash.jpg"
// Get a readable stream to the input file
plaintextF, err := os.Open(fileName)
// ...
defer plaintextF.Close()
// Encrypt the file
encStream, err := client.Encrypt(context.Background(),
plaintextF,
dapr.EncryptOptions{
ComponentName: CryptoComponentName,
// Name of the key to use
// Since this is a symmetric key, we specify AES as key wrapping algorithm
KeyName: SymmetricKeyName,
KeyWrapAlgorithm: "AES",
},
)
// ...
// Write the encrypted data to a file "encrypted.out"
encryptedF, err := os.Create("encrypted.out")
// ...
encryptedF.Close()
fmt.Println("Wrote decrypted data to encrypted.out")
然后应用程序解密大的图像文件:
// Now, decrypt the encrypted data
// First, open the file "encrypted.out" again, this time for reading
encryptedF, err = os.Open("encrypted.out")
// ...
defer encryptedF.Close()
// Now, decrypt the encrypted data
decStream, err := client.Decrypt(context.Background(),
encryptedF,
dapr.DecryptOptions{
// We just need to pass the name of the component
ComponentName: CryptoComponentName,
// Passing the name of the key is optional
KeyName: SymmetricKeyName,
},
)
// ...
// Write the decrypted data to a file "decrypted.out.jpg"
decryptedF, err := os.Create("decrypted.out.jpg")
// ...
decryptedF.Close()
fmt.Println("Wrote decrypted data to decrypted.out.jpg")
}
观看此 Dapr Community Call #83 中的密码学 API 演示视频:
我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
加入我们的 discord 频道 参与讨论。
让我们来看看 Dapr Jobs 构建块,它可以安排作业在特定时间或间隔运行。在本快速入门中,您将使用 Dapr 的 Job API 来调度、获取和删除作业。
您可以通过以下方式尝试此 Jobs 快速入门:
在继续快速入门之前,选择您首选的特定语言的 Dapr SDK。目前,您可以使用 Go SDK 来试验 Jobs API。
此快速入门包含两个应用程序:
job-scheduler.go:调度、检索和删除作业。job-service.go:处理已调度的作业。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 jobs 目录:
cd jobs/go/sdk
使用一个命令运行应用程序并调度作业:
dapr run -f .
预期输出
== APP - job-service == dapr client initializing for: 127.0.0.1:6281
== APP - job-service == Registered job handler for: R2-D2
== APP - job-service == Registered job handler for: C-3PO
== APP - job-service == Registered job handler for: BB-8
== APP - job-service == Starting server on port: 6200
== APP - job-service == Job scheduled: R2-D2
== APP - job-service == Job scheduled: C-3PO
== APP - job-service == 2024/07/17 18:09:59 job:{name:"C-3PO" due_time:"10s" data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}}
== APP - job-scheduler == Get job response: {"droid":"C-3PO","Task":"Memory Wipe"}
== APP - job-service == Job scheduled: BB-8
== APP - job-service == 2024/07/17 18:09:59 job:{name:"BB-8" due_time:"15s" data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}}
== APP - job-scheduler == Get job response: {"droid":"BB-8","Task":"Internal Gyroscope Check"}
== APP - job-scheduler == Deleted job: BB-8
您最终应该会看到在调度器中调度的作业:
$ dapr scheduler list
NAME TARGET BEGIN COUNT LAST TRIGGER
C-3PO job +13.40s 0
R2-D2 job +3.40s 0
5 秒后,终端输出应该显示正在处理的 R2-D2 作业:
== APP - job-service == Starting droid: R2-D2
== APP - job-service == Executing maintenance job: Oil Change
10 秒后,终端输出应该显示正在处理的 C3-PO 作业:
== APP - job-service == Starting droid: C-3PO
== APP - job-service == Executing maintenance job: Memory Wipe
作业将不再在调度器中列出:
$ dapr scheduler list
NAME TARGET BEGIN COUNT LAST TRIGGER
流程完成后,您可以使用单个命令停止并清理应用程序进程。
dapr stop -f .
当您在 Dapr 安装期间运行 dapr init 时:
dapr_scheduler 控制平面与其他 Dapr 服务一起启动。dapr.yaml Multi-App Run 模板文件 在 .dapr/components 目录中生成。在此快速入门中运行 dapr run -f . 会同时启动 job-scheduler 和 job-service。在终端输出中,您可以看到以下作业被调度、检索和删除。
R2-D2 作业正在被调度。C-3PO 作业正在被调度。C-3PO 作业正在被检索。BB-8 作业正在被调度。BB-8 作业正在被检索。BB-8 作业正在被删除。R2-D2 作业在 5 秒后被执行。R2-D2 作业在 10 秒后被执行。dapr.yaml Multi-App Run 模板文件使用 dapr run -f . 运行 Multi-App Run 模板文件 会启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:
version: 1
apps:
- appDirPath: ./job-service/
appID: job-service
appPort: 6200
daprGRPCPort: 6281
appProtocol: grpc
command: ["go", "run", "."]
- appDirPath: ./job-scheduler/
appID: job-scheduler
appPort: 6300
command: ["go", "run", "."]
job-service 应用程序job-service 应用程序创建服务调用处理程序来管理作业的生命周期(scheduleJob、getJob 和 deleteJob)。
if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil {
log.Fatalf("error adding invocation handler: %v", err)
}
if err := server.AddServiceInvocationHandler("getJob", getJob); err != nil {
log.Fatalf("error adding invocation handler: %v", err)
}
if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil {
log.Fatalf("error adding invocation handler: %v", err)
}
接下来,为所有机器人注册作业事件处理程序:
for _, jobName := range jobNames {
if err := server.AddJobEventHandler(jobName, handleJob); err != nil {
log.Fatalf("failed to register job event handler: %v", err)
}
fmt.Println("Registered job handler for: ", jobName)
}
fmt.Println("Starting server on port: " + appPort)
if err = server.Start(); err != nil {
log.Fatalf("failed to start server: %v", err)
}
然后 job-service 调用处理调度、获取、删除和处理作业事件的函数。
// Handler that schedules a DroidJob
func scheduleJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
if in == nil {
err = errors.New("no invocation parameter")
return
}
droidJob := DroidJob{}
err = json.Unmarshal(in.Data, &droidJob)
if err != nil {
fmt.Println("failed to unmarshal job: ", err)
return nil, err
}
jobData := JobData{
Droid: droidJob.Name,
Task: droidJob.Job,
}
content, err := json.Marshal(jobData)
if err != nil {
fmt.Printf("Error marshalling job content")
return nil, err
}
// schedule job
job := daprc.Job{
Name: droidJob.Name,
DueTime: droidJob.DueTime,
Data: &anypb.Any{
Value: content,
},
}
err = app.daprClient.ScheduleJobAlpha1(ctx, &job)
if err != nil {
fmt.Println("failed to schedule job. err: ", err)
return nil, err
}
fmt.Println("Job scheduled: ", droidJob.Name)
out = &common.Content{
Data: in.Data,
ContentType: in.ContentType,
DataTypeURL: in.DataTypeURL,
}
return out, err
}
// Handler that gets a job by name
func getJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
if in == nil {
err = errors.New("no invocation parameter")
return nil, err
}
job, err := app.daprClient.GetJobAlpha1(ctx, string(in.Data))
if err != nil {
fmt.Println("failed to get job. err: ", err)
}
out = &common.Content{
Data: job.Data.Value,
ContentType: in.ContentType,
DataTypeURL: in.DataTypeURL,
}
return out, err
}
// Handler that deletes a job by name
func deleteJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
if in == nil {
err = errors.New("no invocation parameter")
return nil, err
}
err = app.daprClient.DeleteJobAlpha1(ctx, string(in.Data))
if err != nil {
fmt.Println("failed to delete job. err: ", err)
}
out = &common.Content{
Data: in.Data,
ContentType: in.ContentType,
DataTypeURL: in.DataTypeURL,
}
return out, err
}
// Handler that handles job events
func handleJob(ctx context.Context, job *common.JobEvent) error {
var jobData common.Job
if err := json.Unmarshal(job.Data, &jobData); err != nil {
return fmt.Errorf("failed to unmarshal job: %v", err)
}
var jobPayload JobData
if err := json.Unmarshal(job.Data, &jobPayload); err != nil {
return fmt.Errorf("failed to unmarshal payload: %v", err)
}
fmt.Println("Starting droid:", jobPayload.Droid)
fmt.Println("Executing maintenance job:", jobPayload.Task)
return nil
}
job-scheduler 应用程序在 job-scheduler 应用程序中,R2D2、C3PO 和 BB8 作业首先被定义为 []DroidJob:
droidJobs := []DroidJob{
{Name: "R2-D2", Job: "Oil Change", DueTime: "5s"},
{Name: "C-3PO", Job: "Memory Wipe", DueTime: "15s"},
{Name: "BB-8", Job: "Internal Gyroscope Check", DueTime: "30s"},
}
然后使用 Jobs API 调度、检索和删除作业。正如您从终端输出中看到的那样,首先调度 R2D2 作业:
// Schedule R2D2 job
err = schedule(droidJobs[0])
if err != nil {
log.Fatalln("Error scheduling job: ", err)
}
然后调度 C3PO 作业并返回作业数据:
// Schedule C-3PO job
err = schedule(droidJobs[1])
if err != nil {
log.Fatalln("Error scheduling job: ", err)
}
// Get C-3PO job
resp, err := get(droidJobs[1])
if err != nil {
log.Fatalln("Error retrieving job: ", err)
}
fmt.Println("Get job response: ", resp)
然后调度、检索和删除 BB8 作业:
// Schedule BB-8 job
err = schedule(droidJobs[2])
if err != nil {
log.Fatalln("Error scheduling job: ", err)
}
// Get BB-8 job
resp, err = get(droidJobs[2])
if err != nil {
log.Fatalln("Error retrieving job: ", err)
}
fmt.Println("Get job response: ", resp)
// Delete BB-8 job
err = delete(droidJobs[2])
if err != nil {
log.Fatalln("Error deleting job: ", err)
}
fmt.Println("Job deleted: ", droidJobs[2].Name)
job-scheduler.go 还定义了 schedule、get 和 delete 函数,从 job-service.go 调用。
// Schedules a job by invoking grpc service from job-service passing a DroidJob as an argument
func schedule(droidJob DroidJob) error {
jobData, err := json.Marshal(droidJob)
if err != nil {
fmt.Println("Error marshalling job content")
return err
}
content := &daprc.DataContent{
ContentType: "application/json",
Data: []byte(jobData),
}
// Schedule Job
_, err = app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "scheduleJob", "POST", content)
if err != nil {
fmt.Println("Error invoking method: ", err)
return err
}
return nil
}
// Gets a job by invoking grpc service from job-service passing a job name as an argument
func get(droidJob DroidJob) (string, error) {
content := &daprc.DataContent{
ContentType: "text/plain",
Data: []byte(droidJob.Name),
}
//get job
resp, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "getJob", "GET", content)
if err != nil {
fmt.Println("Error invoking method: ", err)
return "", err
}
return string(resp), nil
}
// Deletes a job by invoking grpc service from job-service passing a job name as an argument
func delete(droidJob DroidJob) error {
content := &daprc.DataContent{
ContentType: "text/plain",
Data: []byte(droidJob.Name),
}
_, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "deleteJob", "DELETE", content)
if err != nil {
fmt.Println("Error invoking method: ", err)
return err
}
return nil
}
此快速入门包含两个应用程序:
job-scheduler.go:调度、检索和删除作业。job-service.go:处理已调度的作业。对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 jobs 目录:
cd jobs/go/sdk
在终端中,运行 job-service 应用程序:
dapr run --app-id job-service --app-port 6200 --dapr-grpc-port 6281 --app-protocol grpc -- go run .
预期输出
== APP == dapr client initializing for: 127.0.0.1:6281
== APP == Registered job handler for: R2-D2
== APP == Registered job handler for: C-3PO
== APP == Registered job handler for: BB-8
== APP == Starting server on port: 6200
在新的终端窗口中,运行 job-scheduler 应用程序:
dapr run --app-id job-scheduler --app-port 6300 -- go run .
预期输出
== APP == dapr client initializing for:
== APP == Get job response: {"droid":"C-3PO","Task":"Memory Wipe"}
== APP == Get job response: {"droid":"BB-8","Task":"Internal Gyroscope Check"}
== APP == Job deleted: BB-8
返回 job-service 应用程序终端窗口。输出应该是:
== APP == Job scheduled: R2-D2
== APP == Job scheduled: C-3PO
== APP == 2024/07/17 18:25:36 job:{name:"C-3PO" due_time:"10s" data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}}
== APP == Job scheduled: BB-8
== APP == 2024/07/17 18:25:36 job:{name:"BB-8" due_time:"15s" data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}}
== APP == Starting droid: R2-D2
== APP == Executing maintenance job: Oil Change
== APP == Starting droid: C-3PO
== APP == Executing maintenance job: Memory Wipe
了解当您运行 dapr run 时 job-service 和 job-scheduler 应用程序中发生了什么。
观看使用 Go HTTP 示例演示 Jobs API 的实际操作,录制于 Dapr Community Call #107。
我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您是否觉得此快速入门有帮助?您有改进建议吗?
加入我们的 Discord 频道 进行讨论。
让我们来看看 Dapr 对话构建块 如何简化与大语言模型(LLMs)的交互。在这个快速入门中,你使用 echo 组件与模拟 LLM 通信,并要求它定义 Dapr。
你可以通过以下方式尝试此对话快速入门:
在继续快速入门之前,请选择你首选的语言特定的 Dapr SDK。
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/python/sdk/conversation
安装依赖项:
pip3 install -r requirements.txt
dapr run -f .
注意:由于 Windows 中未定义 Python3.exe,你可能需要使用
python app.py而不是python3 app.py。
预期输出
== APP - conversation == Input sent: What is dapr?
== APP - conversation == Output response: What is dapr?
在此快速入门中运行 dapr run -f . 会启动 app.py。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行 多应用运行模板文件 会启动项目中的所有应用。此快速入门只有一个应用,因此 dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: conversation
appDirPath: ./conversation/
command: ["python3", "app.py"]
在 quickstart 的 conversation/components 目录中,conversation.yaml 文件 配置了 echo LLM 组件。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: echo
spec:
type: conversation.echo
version: v1
要与真实的 LLM 交互,请将模拟组件替换为支持的对话组件之一。例如,要使用 OpenAI 组件,请参阅对话操作指南中的示例
app.py 对话应用在应用代码中:
from dapr.clients import DaprClient
from dapr.clients.grpc.conversation import ConversationInputAlpha2, ConversationMessage, ConversationMessageContent, ConversationMessageOfUser
with DaprClient() as d:
text_input = "What is dapr?"
provider_component = "echo"
inputs = [
ConversationInputAlpha2(messages=[ConversationMessage(of_user=ConversationMessageOfUser(content=[ConversationMessageContent(text=text_input)]))],
scrub_pii=True),
]
print(f'Input sent: {text_input}')
response = d.converse_alpha2(name=provider_component, inputs=inputs, temperature=0.7, context_id='chat-123')
for output in response.outputs:
print(f'Output response: {output.choices[0].message.content}')
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/javascript/http/conversation
安装依赖项:
npm install
返回到 http 目录,并使用以下命令启动对话服务:
dapr run -f .
预期输出
== APP - conversation == Conversation input sent: What is dapr?
== APP - conversation == Output response: What is dapr?
== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?
== APP - conversation == Output message: { outputs: [ { choices: [Array] } ] }
== APP - conversation == Output message: What is the weather like in San Francisco in celsius?
== APP - conversation == Tool calls detected: [{"id":"0","function":{"name":"get_weather","arguments":"location,unit"}}]
在此快速入门中运行 dapr run -f . 会启动 conversation.go。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行 多应用运行模板文件 会启动项目中的所有应用。此快速入门只有一个应用,因此 dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appID: conversation
appDirPath: ./conversation/
daprHTTPPort: 3502
command: ["npm", "run", "start"]
在 quickstart 的 conversation/components 目录中,conversation.yaml 文件 配置了 echo LLM 组件。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: echo
spec:
type: conversation.echo
version: v1
要与真实的 LLM 交互,请将模拟组件替换为支持的对话组件之一。例如,要使用 OpenAI 组件,请参阅对话操作指南中的示例
index.js 对话应用在应用代码的第一部分中:
const daprHost = process.env.DAPR_HOST || "http://localhost";
const daprHttpPort = process.env.DAPR_HTTP_PORT || "3500";
const reqURL = `${daprHost}:${daprHttpPort}/v1.0-alpha2/conversation/${conversationComponentName}/converse`;
// Plain conversation
try {
const converseInputBody = {
inputs: [
{
messages: [
{
ofUser: {
content: [
{
text: "What is dapr?",
},
],
},
},
],
},
],
parameters: {},
metadata: {},
};
const response = await fetch(reqURL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(converseInputBody),
});
console.log("Conversation input sent: What is dapr?");
const data = await response.json();
const result = data.outputs[0].choices[0].message.content;
console.log("Output response:", result);
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
}
在应用代码的第二部分中:
get_weather 的定义。try {
const toolCallingInputBody = {
inputs: [
{
messages: [
{
ofUser: {
content: [
{
text: "What is the weather like in San Francisco in celsius?",
},
],
},
},
],
scrubPii: false,
},
],
metadata: {
api_key: "test-key",
version: "1.0",
},
scrubPii: false,
temperature: 0.7,
tools: [
{
function: {
name: "get_weather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "The temperature unit to use",
},
},
required: ["location"],
},
},
},
],
toolChoice: "auto",
};
const response = await fetch(reqURL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(toolCallingInputBody),
});
console.log(
"Tool calling input sent: What is the weather like in San Francisco in celsius?"
);
const data = await response.json();
const result = data?.outputs?.[0]?.choices?.[0]?.message?.content;
console.log("Output message:", result);
if (data?.outputs?.[0]?.choices?.[0]?.message?.toolCalls) {
console.log(
"Tool calls detected:",
JSON.stringify(data.outputs[0].choices[0].message?.toolCalls)
);
} else {
console.log("No tool calls in response");
}
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/csharp/sdk
使用以下命令启动对话服务:
dapr run -f .
预期输出
== APP - conversation == Conversation input sent: What is dapr?
== APP - conversation == Output response: What is dapr?
== APP - conversation == Tool calling input sent: What is the weather like in San Francisco in celsius?
== APP - conversation == Output message: What is the weather like in San Francisco in celsius?
== APP - conversation == Tool calls detected:
== APP - conversation == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
== APP - conversation == Function name: get_weather
== APP - conversation == Function arguments: location,unit
在此快速入门中运行 dapr run -f . 会启动 conversation Program.cs。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行 多应用运行模板文件 会启动项目中的所有应用。此快速入门只有一个应用,因此 dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appDirPath: ./conversation/
appID: conversation
daprHTTPPort: 3500
command: ["dotnet", "run"]
在 conversation/components 目录中,conversation.yaml 文件 配置了 echo 模拟 LLM 组件。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: echo
spec:
type: conversation.echo
version: v1
要与真实的 LLM 交互,请将模拟组件替换为支持的对话组件之一。例如,要使用 OpenAI 组件,请参阅对话操作指南中的示例
Program.cs 对话应用在应用代码中:
get_weather 的定义。using System.Text.Json;
using Dapr.AI.Conversation;
using Dapr.AI.Conversation.ConversationRoles;
using Dapr.AI.Conversation.Extensions;
using Dapr.AI.Conversation.Tools;
const string conversationComponentName = "echo";
const string conversationText = "What is dapr?";
const string toolCallInput = "What is the weather like in San Francisco in celsius?";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprConversationClient();
var app = builder.Build();
//
// Setup
var conversationClient = app.Services.GetRequiredService<DaprConversationClient>();
var conversationOptions = new ConversationOptions(conversationComponentName)
{
ScrubPII = false,
ToolChoice = ToolChoice.Auto,
Temperature = 0.7,
Tools = [
new ToolFunction("function")
{
Name = "get_weather",
Description = "Get the current weather for a location",
Parameters = JsonSerializer.Deserialize<Dictionary<string, object?>>("""
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use"
}
},
"required": ["location"]
}
""") ?? throw new("Unable to parse tool function parameters."),
},
],
};
//
// Simple Conversation
var conversationResponse = await conversationClient.ConverseAsync(
[new ConversationInput(new List<IConversationMessage>
{
new UserMessage {
Name = "TestUser",
Content = [
new MessageContent(conversationText),
],
},
})],
conversationOptions
);
Console.WriteLine($"Conversation input sent: {conversationText}");
Console.WriteLine($"Output response: {conversationResponse.Outputs.First().Choices.First().Message.Content}");
//
// Tool Calling
var toolCallResponse = await conversationClient.ConverseAsync(
[new ConversationInput(new List<IConversationMessage>
{
new UserMessage {
Name = "TestUser",
Content = [
new MessageContent(toolCallInput),
],
},
})],
conversationOptions
);
Console.WriteLine($"Tool calling input sent: {toolCallInput}");
Console.WriteLine($"Output message: {toolCallResponse.Outputs.First().Choices.First().Message.Content}");
Console.WriteLine("Tool calls detected:");
var functionToolCall = toolCallResponse.Outputs.First().Choices.First().Message.ToolCalls.First() as CalledToolFunction
?? throw new("Unexpected tool call type for demo.");
var toolCallJson = JsonSerializer.Serialize(new
{
id = 0,
function = new
{
name = functionToolCall.Name,
arguments = functionToolCall.JsonArguments,
},
});
Console.WriteLine($"Tool call: {toolCallJson}");
Console.WriteLine($"Function name: {functionToolCall.Name}");
Console.WriteLine($"Function arguments: {functionToolCall.JsonArguments}");
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/java/sdk/conversation
安装依赖项:
mvn clean install
返回到 sdk 目录,并使用以下命令启动对话服务:
dapr run -f .
预期输出
== APP - conversation == Input: What is Dapr?
== APP - conversation == Output response: What is Dapr?
在此快速入门中运行 dapr run -f . 会启动 Conversation.java。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行 多应用运行模板文件 会启动项目中的所有应用。此快速入门只有一个应用,因此 dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components
apps:
- appID: conversation
appDirPath: ./conversation/target
command: ["java", "-jar", "ConversationAIService-0.0.1-SNAPSHOT.jar"]
在 quickstart 的 conversation/components 目录中,conversation.yaml 文件 配置了 echo LLM 组件。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: echo
spec:
type: conversation.echo
version: v1
要与真实的 LLM 交互,请将模拟组件替换为支持的对话组件之一。例如,要使用 OpenAI 组件,请参阅对话操作指南中的示例
Conversation.java 对话应用在应用代码中:
package com.service;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient;
import io.dapr.client.domain.ConversationInput;
import io.dapr.client.domain.ConversationRequest;
import io.dapr.client.domain.ConversationResponse;
import reactor.core.publisher.Mono;
import java.util.List;
public class Conversation {
public static void main(String[] args) {
String prompt = "What is Dapr?";
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
System.out.println("Input: " + prompt);
ConversationInput daprConversationInput = new ConversationInput(prompt);
// Component name is the name provided in the metadata block of the conversation.yaml file.
Mono<ConversationResponse> responseMono = client.converse(new ConversationRequest("echo",
List.of(daprConversationInput))
.setContextId("contextId")
.setScrubPii(true).setTemperature(1.1d));
ConversationResponse response = responseMono.block();
System.out.printf("Output response: %s", response.getConversationOutputs().get(0).getResult());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/go/sdk
使用以下命令启动对话服务:
dapr run -f .
预期输出
== APP - conversation-sdk == Input sent: What is dapr?
== APP - conversation-sdk == Output response: What is dapr?
== APP - conversation-sdk == Tool calling input sent: What is the weather like in San Francisco in celsius?'
== APP - conversation-sdk == Tool Call: Name: getWeather - Arguments: location,unit
== APP - conversation-sdk == Tool Call Output: The weather in San Francisco is 25 degrees Celsius
在此快速入门中运行 dapr run -f . 会启动 conversation.go。
dapr.yaml 多应用运行模板文件使用 dapr run -f . 运行 多应用运行模板文件 会启动项目中的所有应用。此快速入门只有一个应用,因此 dapr.yaml 文件包含以下内容:
version: 1
common:
resourcesPath: ../../components/
apps:
- appDirPath: ./conversation/
appID: conversation
daprHTTPPort: 3501
command: ["go", "run", "."]
在 quickstart 的 conversation/components 目录中,conversation.yaml 文件 配置了 echo LLM 组件。
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: echo
spec:
type: conversation.echo
version: v1
要与真实的 LLM 交互,请将模拟组件替换为支持的对话组件之一。例如,要使用 OpenAI 组件,请参阅对话操作指南中的示例
conversation.go 对话应用在应用代码中:
get_weather 的定义。import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"github.com/invopop/jsonschema"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
dapr "github.com/dapr/go-sdk/client"
)
// createMapOfArgsForEcho is a helper function to deal with the issue with the echo component not returning args as a map but in csv format
func createMapOfArgsForEcho(s string) ([]byte, error) {
m := map[string]any{}
for _, p := range strings.Split(s, ",") {
m[p] = p
}
return json.Marshal(m)
}
// getWeatherInLocation is an example function to use as a tool call
func getWeatherInLocation(request GetDegreesWeatherRequest, defaultValues GetDegreesWeatherRequest) string {
location := request.Location
unit := request.Unit
if location == "location" {
location = defaultValues.Location
}
if unit == "unit" {
unit = defaultValues.Unit
}
return fmt.Sprintf("The weather in %s is 25 degrees %s", location, unit)
}
type GetDegreesWeatherRequest struct {
Location string `json:"location" jsonschema:"title=Location,description=The location to look up the weather for"`
Unit string `json:"unit" jsonschema:"enum=celsius,enum=fahrenheit,description=Unit"`
}
// GenerateFunctionTool helper method to create jsonschema input
func GenerateFunctionTool[T any](name, description string) (*dapr.ConversationToolsAlpha2, error) {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
DoNotReference: true,
}
var v T
schema := reflector.Reflect(v)
schemaBytes, err := schema.MarshalJSON()
if err != nil {
return nil, err
}
var protoStruct structpb.Struct
if err := protojson.Unmarshal(schemaBytes, &protoStruct); err != nil {
return nil, fmt.Errorf("converting jsonschema to proto Struct: %w", err)
}
return (*dapr.ConversationToolsAlpha2)(&dapr.ConversationToolsFunctionAlpha2{
Name: name,
Description: &description,
Parameters: &protoStruct,
}), nil
}
// createUserMessageInput is a helper method to create user messages in expected proto format
func createUserMessageInput(msg string) *dapr.ConversationInputAlpha2 {
return &dapr.ConversationInputAlpha2{
Messages: []*dapr.ConversationMessageAlpha2{
{
ConversationMessageOfUser: &dapr.ConversationMessageOfUserAlpha2{
Content: []*dapr.ConversationMessageContentAlpha2{
{
Text: &msg,
},
},
},
},
},
}
}
func main() {
client, err := dapr.NewClient()
if err != nil {
panic(err)
}
inputMsg := "What is dapr?"
conversationComponent := "echo"
request := dapr.ConversationRequestAlpha2{
Name: conversationComponent,
Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(inputMsg)},
}
fmt.Println("Input sent:", inputMsg)
resp, err := client.ConverseAlpha2(context.Background(), request)
if err != nil {
log.Fatalf("err: %v", err)
}
fmt.Println("Output response:", resp.Outputs[0].Choices[0].Message.Content)
tool, err := GenerateFunctionTool[GetDegreesWeatherRequest]("getWeather", "get weather from a location in the given unit")
if err != nil {
log.Fatalf("err: %v", err)
}
weatherMessage := "Tool calling input sent: What is the weather like in San Francisco in celsius?'"
requestWithTool := dapr.ConversationRequestAlpha2{
Name: conversationComponent,
Inputs: []*dapr.ConversationInputAlpha2{createUserMessageInput(weatherMessage)},
Tools: []*dapr.ConversationToolsAlpha2{tool},
}
resp, err = client.ConverseAlpha2(context.Background(), requestWithTool)
if err != nil {
log.Fatalf("err: %v", err)
}
fmt.Println(resp.Outputs[0].Choices[0].Message.Content)
for _, toolCalls := range resp.Outputs[0].Choices[0].Message.ToolCalls {
fmt.Printf("Tool Call: Name: %s - Arguments: %v\n", toolCalls.ToolTypes.Name, toolCalls.ToolTypes.Arguments)
// parse the arguments and execute tool
args := []byte(toolCalls.ToolTypes.Arguments)
if conversationComponent == "echo" {
// The echo component does not return a compliant tool calling response in json format but rather returns a csv
args, err = createMapOfArgsForEcho(toolCalls.ToolTypes.Arguments)
if err != nil {
log.Fatalf("err: %v", err)
}
}
// find the tool (only one in this case) and execute
for _, toolInfo := range requestWithTool.Tools {
if toolInfo.Name == toolCalls.ToolTypes.Name && toolInfo.Name == "getWeather" {
var reqArgs GetDegreesWeatherRequest
if err = json.Unmarshal(args, &reqArgs); err != nil {
log.Fatalf("err: %v", err)
}
// execute tool
toolExecutionOutput := getWeatherInLocation(reqArgs, GetDegreesWeatherRequest{Location: "San Francisco", Unit: "Celsius"})
fmt.Printf("Tool Call Output: %s\n", toolExecutionOutput)
}
}
}
}
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/python/sdk/conversation
安装依赖项:
pip3 install -r requirements.txt
返回到 sdk 目录,并使用以下命令启动对话服务:
dapr run --app-id conversation --resources-path ../../../components -- python3 app.py
注意:由于 Windows 中未定义 Python3.exe,你可能需要使用
python app.py而不是python3 app.py。
预期输出
== APP - conversation == Input sent: What is dapr?
== APP - conversation == Output response: What is dapr?
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/javascript/http/conversation
安装依赖项:
npm install
dapr run --app-id conversation --resources-path ../../../components -- npm run start
预期输出
== APP == Conversation input sent: What is dapr?
== APP == Output response: What is dapr?
== APP == Tool calling input sent: What is the weather like in San Francisco in celsius?
== APP == Output message: What is the weather like in San Francisco in celsius?
== APP == Tool calls detected: [{"id":"0","function":{"name":"get_weather","arguments":"location,unit"}}]
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/csharp/sdk/conversation
安装依赖项:
dotnet build
使用以下命令启动对话服务:
dapr run --app-id conversation --resources-path ../../../components/ -- dotnet run
预期输出
== APP == Conversation input sent: What is dapr?
== APP == Output response: What is dapr?
== APP == Tool calling input sent: What is the weather like in San Francisco in celsius?
== APP == Output message: What is the weather like in San Francisco in celsius?
== APP == Tool calls detected:
== APP == Tool call: {"id":0,"function":{"name":"get_weather","arguments":"location,unit"}}
== APP == Function name: get_weather
== APP == Function arguments: location,unit
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/java/sdk/conversation
安装依赖项:
mvn clean install
使用以下命令启动对话服务:
dapr run --app-id conversation --resources-path ../../../components/ -- java -jar target/ConversationAIService-0.0.1-SNAPSHOT.jar com.service.Conversation
预期输出
== APP == Input: What is Dapr?
== APP == Output response: What is Dapr?
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
从 Quickstarts 目录的根目录,导航到 conversation 目录:
cd conversation/go/sdk/conversation
安装依赖项:
go build .
使用以下命令启动对话服务:
dapr run --app-id conversation --resources-path ../../../components/ -- go run .
预期输出
== APP == dapr client initializing for: 127.0.0.1:53826
== APP == Input sent: What is dapr?
== APP == Output response: What is dapr?
== APP == Tool calling input sent: What is the weather like in San Francisco in celsius?'
== APP == Tool Call: Name: getWeather - Arguments: location,unit
== APP == Tool Call Output: The weather in San Francisco is 25 degrees Celsius
观看 Diagrid 的 Dapr v1.15 庆祝活动 中展示的演示,了解对话 API 如何使用 .NET SDK 工作。
我们一直在努力改进我们的快速入门示例,并重视你的反馈。你是否觉得这个快速入门有帮助?你有改进建议吗?
加入我们的 discord 频道 参与讨论。
通过模拟系统故障来观察 Dapr 的弹性能力。在本快速入门中,你将:

在继续快速入门之前,请选择你偏好的特定语言的 Dapr SDK。
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd ../state_management/python/sdk/order-processor
安装依赖项
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。Dapr 边车随后会加载位于资源目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- order-processor
spec:
policies:
retries:
retryForever:
policy: constant
duration: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- python3
应用程序启动后,order-processor 服务会向 statestore.yaml 组件中定义的 statestore Redis 实例写入和读取 orderId 键值对。
== APP == Saving Order: { orderId: '1' }
== APP == Getting Order: { orderId: '1' }
== APP == Saving Order: { orderId: '2' }
== APP == Getting Order: { orderId: '2' }
== APP == Saving Order: { orderId: '3' }
== APP == Getting Order: { orderId: '3' }
== APP == Saving Order: { orderId: '4' }
== APP == Getting Order: { orderId: '4' }
通过停止在开发机器上执行 dapr init 时初始化的 Redis 容器实例来模拟故障。实例停止后,order-processor 服务的写入和读取操作将开始失败。
由于 resiliency.yaml 规范将 statestore 定义为组件目标,因此所有失败的请求都将应用重试和熔断器策略:
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
在新的终端窗口中,运行以下命令来停止 Redis:
docker stop dapr_redis
Redis 停止后,请求将开始失败,并应用名为 retryForever 的重试策略。下面的输出显示了 order-processor 服务的日志:
INFO[0006] Error processing operation component[statestore] output. Retrying...
根据 retryForever 策略,对于每个失败的请求,将以 5 秒的间隔无限期地继续重试。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,熔断器策略 simpleCB 被触发,熔断器打开,停止所有请求:
INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
5 秒后,熔断器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳回打开状态。
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
当你在机器上重新启动 Redis 容器后,应用程序将无缝恢复,从中断的地方继续执行。
docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.
== APP == Saving Order: { orderId: '5' }
== APP == Getting Order: { orderId: '5' }
== APP == Saving Order: { orderId: '6' }
== APP == Getting Order: { orderId: '6' }
== APP == Saving Order: { orderId: '7' }
== APP == Getting Order: { orderId: '7' }
== APP == Saving Order: { orderId: '8' }
== APP == Getting Order: { orderId: '8' }
== APP == Saving Order: { orderId: '9' }
== APP == Getting Order: { orderId: '9' }
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd ../state_management/javascript/sdk/order-processor
安装依赖项
npm install
运行 order-processor 服务以及 Dapr 边车。Dapr 边车随后会加载位于资源目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm start
应用程序启动后,order-processor 服务会向 statestore.yaml 组件中定义的 statestore Redis 实例写入和读取 orderId 键值对。
== APP == Saving Order: { orderId: '1' }
== APP == Getting Order: { orderId: '1' }
== APP == Saving Order: { orderId: '2' }
== APP == Getting Order: { orderId: '2' }
== APP == Saving Order: { orderId: '3' }
== APP == Getting Order: { orderId: '3' }
== APP == Saving Order: { orderId: '4' }
== APP == Getting Order: { orderId: '4' }
通过停止在开发机器上执行 dapr init 时初始化的 Redis 容器实例来模拟故障。实例停止后,order-processor 服务的写入和读取操作将开始失败。
由于 resiliency.yaml 规范将 statestore 定义为组件目标,因此所有失败的请求都将应用重试和熔断器策略:
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
在新的终端窗口中,运行以下命令来停止 Redis:
docker stop dapr_redis
Redis 停止后,请求将开始失败,并应用名为 retryForever 的重试策略。下面的输出显示了 order-processor 服务的日志:
INFO[0006] Error processing operation component[statestore] output. Retrying...
根据 retryForever 策略,对于每个失败的请求,将以 5 秒的间隔无限期地继续重试。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,熔断器策略 simpleCB 被触发,熔断器打开,停止所有请求:
INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
5 秒后,熔断器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳回打开状态。
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
当你在机器上重新启动 Redis 容器后,应用程序将无缝恢复,从中断的地方继续执行。
docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.
== APP == Saving Order: { orderId: '5' }
== APP == Getting Order: { orderId: '5' }
== APP == Saving Order: { orderId: '6' }
== APP == Getting Order: { orderId: '6' }
== APP == Saving Order: { orderId: '7' }
== APP == Getting Order: { orderId: '7' }
== APP == Saving Order: { orderId: '8' }
== APP == Getting Order: { orderId: '8' }
== APP == Saving Order: { orderId: '9' }
== APP == Getting Order: { orderId: '9' }
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd ../state_management/csharp/sdk/order-processor
安装依赖项
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。Dapr 边车随后会加载位于资源目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run
应用程序启动后,order-processor 服务会向 statestore.yaml 组件中定义的 statestore Redis 实例写入和读取 orderId 键值对。
== APP == Saving Order: { orderId: '1' }
== APP == Getting Order: { orderId: '1' }
== APP == Saving Order: { orderId: '2' }
== APP == Getting Order: { orderId: '2' }
== APP == Saving Order: { orderId: '3' }
== APP == Getting Order: { orderId: '3' }
== APP == Saving Order: { orderId: '4' }
== APP == Getting Order: { orderId: '4' }
通过停止在开发机器上执行 dapr init 时初始化的 Redis 容器实例来模拟故障。实例停止后,order-processor 服务的写入和读取操作将开始失败。
由于 resiliency.yaml 规范将 statestore 定义为组件目标,因此所有失败的请求都将应用重试和熔断器策略:
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
在新的终端窗口中,运行以下命令来停止 Redis:
docker stop dapr_redis
Redis 停止后,请求将开始失败,并应用名为 retryForever 的重试策略。下面的输出显示了 order-processor 服务的日志:
INFO[0006] Error processing operation component[statestore] output. Retrying...
根据 retryForever 策略,对于每个失败的请求,将以 5 秒的间隔无限期地继续重试。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,熔断器策略 simpleCB 被触发,熔断器打开,停止所有请求:
INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
5 秒后,熔断器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳回打开状态。
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
当你在机器上重新启动 Redis 容器后,应用程序将无缝恢复,从中断的地方继续执行。
docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.
== APP == Saving Order: { orderId: '5' }
== APP == Getting Order: { orderId: '5' }
== APP == Saving Order: { orderId: '6' }
== APP == Getting Order: { orderId: '6' }
== APP == Saving Order: { orderId: '7' }
== APP == Getting Order: { orderId: '7' }
== APP == Saving Order: { orderId: '8' }
== APP == Getting Order: { orderId: '8' }
== APP == Saving Order: { orderId: '9' }
== APP == Getting Order: { orderId: '9' }
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd ../state_management/java/sdk/order-processor
安装依赖项
mvn clean install
运行 order-processor 服务以及 Dapr 边车。Dapr 边车随后会加载位于资源目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
应用程序启动后,order-processor 服务会向 statestore.yaml 组件中定义的 statestore Redis 实例写入和读取 orderId 键值对。
== APP == Saving Order: { orderId: '1' }
== APP == Getting Order: { orderId: '1' }
== APP == Saving Order: { orderId: '2' }
== APP == Getting Order: { orderId: '2' }
== APP == Saving Order: { orderId: '3' }
== APP == Getting Order: { orderId: '3' }
== APP == Saving Order: { orderId: '4' }
== APP == Getting Order: { orderId: '4' }
通过停止在开发机器上执行 dapr init 时初始化的 Redis 容器实例来模拟故障。实例停止后,order-processor 服务的写入和读取操作将开始失败。
由于 resiliency.yaml 规范将 statestore 定义为组件目标,因此所有失败的请求都将应用重试和熔断器策略:
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
在新的终端窗口中,运行以下命令来停止 Redis:
docker stop dapr_redis
Redis 停止后,请求将开始失败,并应用名为 retryForever 的重试策略。下面的输出显示了 order-processor 服务的日志:
INFO[0006] Error processing operation component[statestore] output. Retrying...
根据 retryForever 策略,对于每个失败的请求,将以 5 秒的间隔无限期地继续重试。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,熔断器策略 simpleCB 被触发,熔断器打开,停止所有请求:
INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
5 秒后,熔断器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳回打开状态。
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
当你在机器上重新启动 Redis 容器后,应用程序将无缝恢复,从中断的地方继续执行。
docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.
== APP == Saving Order: { orderId: '5' }
== APP == Getting Order: { orderId: '5' }
== APP == Saving Order: { orderId: '6' }
== APP == Getting Order: { orderId: '6' }
== APP == Saving Order: { orderId: '7' }
== APP == Getting Order: { orderId: '7' }
== APP == Saving Order: { orderId: '8' }
== APP == Getting Order: { orderId: '8' }
== APP == Saving Order: { orderId: '9' }
== APP == Getting Order: { orderId: '9' }
对于此示例,你需要:
git clone https://github.com/dapr/quickstarts.git
在终端窗口中,导航到 order-processor 目录。
cd ../state_management/go/sdk/order-processor
安装依赖项
go build .
运行 order-processor 服务以及 Dapr 边车。Dapr 边车随后会加载位于资源目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources -- go run .
应用程序启动后,order-processor 服务会向 statestore.yaml 组件中定义的 statestore Redis 实例写入和读取 orderId 键值对。
== APP == Saving Order: { orderId: '1' }
== APP == Getting Order: { orderId: '1' }
== APP == Saving Order: { orderId: '2' }
== APP == Getting Order: { orderId: '2' }
== APP == Saving Order: { orderId: '3' }
== APP == Getting Order: { orderId: '3' }
== APP == Saving Order: { orderId: '4' }
== APP == Getting Order: { orderId: '4' }
通过停止在开发机器上执行 dapr init 时初始化的 Redis 容器实例来模拟故障。实例停止后,order-processor 服务的写入和读取操作将开始失败。
由于 resiliency.yaml 规范将 statestore 定义为组件目标,因此所有失败的请求都将应用重试和熔断器策略:
targets:
components:
statestore:
outbound:
retry: retryForever
circuitBreaker: simpleCB
在新的终端窗口中,运行以下命令来停止 Redis:
docker stop dapr_redis
Redis 停止后,请求将开始失败,并应用名为 retryForever 的重试策略。下面的输出显示了 order-processor 服务的日志:
INFO[0006] Error processing operation component[statestore] output. Retrying...
根据 retryForever 策略,对于每个失败的请求,将以 5 秒的间隔无限期地继续重试。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,熔断器策略 simpleCB 被触发,熔断器打开,停止所有请求:
INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
5 秒后,熔断器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳回打开状态。
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
当你在机器上重新启动 Redis 容器后,应用程序将无缝恢复,从中断的地方继续执行。
docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.
== APP == Saving Order: { orderId: '5' }
== APP == Getting Order: { orderId: '5' }
== APP == Saving Order: { orderId: '6' }
== APP == Getting Order: { orderId: '6' }
== APP == Saving Order: { orderId: '7' }
== APP == Getting Order: { orderId: '7' }
== APP == Saving Order: { orderId: '8' }
== APP == Getting Order: { orderId: '8' }
== APP == Saving Order: { orderId: '9' }
== APP == Getting Order: { orderId: '9' }
我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?
在我们的 discord 频道 中加入讨论。
了解有关弹性功能的更多信息,以及它如何与 Dapr 的构建块 API 配合使用。
探索 Dapr 教程 >>通过模拟系统故障来观察 Dapr 弹性功能。在本快速入门中,您将:
checkout 和 order-processor。checkout 将持续向 order-processor 发起 Dapr 服务调用请求。
在继续快速入门之前,选择您偏好的特定语言的 Dapr SDK。
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门目录的根目录导航到 order-processor 目录。
cd service_invocation/python/http/order-processor
安装依赖:
pip3 install -r requirements.txt
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 8001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- python3 app.py
checkout 服务应用程序在新的终端窗口中,从快速入门目录的根目录导航到 checkout 目录。
cd service_invocation/python/http/checkout
安装依赖:
pip3 install -r requirements.txt
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- python3 app.py
然后 Dapr 边车将加载位于 resources 目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
当两个服务和边车都在运行时,注意订单是如何使用 Dapr 服务调用从 checkout 服务传递到 order-processor 服务的。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
通过停止 order-processor 服务来模拟故障。一旦实例停止,来自 checkout 服务的服务调用操作将开始失败。
由于 resiliency.yaml 规范将 order-processor 服务定义为弹性目标,所有失败的请求都将应用重试和断路器策略:
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
在 order-processor 窗口中,停止服务:
CMD + C
CTRL + C
一旦第一个请求失败,将应用名为 retryForever 的重试策略:
INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...
对于每个失败的请求,重试将以 5 秒的间隔无限期继续。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,断路器策略 simpleCB 将被触发,断路器打开,停止所有请求:
INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
超过 5 秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳闸回到打开状态。
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
只要 order-processor 服务停止,这种半开/打开行为就会持续下去。
重新启动 order-processor 服务后,应用程序将无缝恢复,从中断的地方继续接受订单请求。
在 order-processor 服务终端中,重新启动应用程序:
dapr run --app-port 8001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- python3 app.py
checkout 服务输出:
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门目录的根目录导航到 order-processor 目录。
cd service_invocation/javascript/http/order-processor
安装依赖:
npm install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 5001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- npm start
checkout 服务应用程序在新的终端窗口中,从快速入门目录的根目录导航到 checkout 目录。
cd service_invocation/javascript/http/checkout
安装依赖:
npm install
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- npm start
然后 Dapr 边车将加载位于 resources 目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
当两个服务和边车都在运行时,注意订单是如何使用 Dapr 服务调用从 checkout 服务传递到 order-processor 服务的。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
通过停止 order-processor 服务来模拟故障。一旦实例停止,来自 checkout 服务的服务调用操作将开始失败。
由于 resiliency.yaml 规范将 order-processor 服务定义为弹性目标,所有失败的请求都将应用重试和断路器策略:
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
在 order-processor 窗口中,停止服务:
CMD + C
CTRL + C
一旦第一个请求失败,将应用名为 retryForever 的重试策略:
INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...
对于每个失败的请求,重试将以 5 秒的间隔无限期继续。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,断路器策略 simpleCB 将被触发,断路器打开,停止所有请求:
INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
超过 5 秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳闸回到打开状态。
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
重新启动 order-processor 服务后,应用程序将无缝恢复,从中断的地方继续。
在 order-processor 服务终端中,重新启动应用程序:
dapr run --app-port 5001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- npm start
checkout 服务输出:
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门目录的根目录导航到 order-processor 目录。
cd service_invocation/csharp/http/order-processor
安装依赖:
dotnet restore
dotnet build
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 7001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- dotnet run
checkout 服务应用程序在新的终端窗口中,从快速入门目录的根目录导航到 checkout 目录。
cd service_invocation/csharp/http/checkout
安装依赖:
dotnet restore
dotnet build
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- dotnet run
然后 Dapr 边车将加载位于 resources 目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
当两个服务和边车都在运行时,注意订单是如何使用 Dapr 服务调用从 checkout 服务传递到 order-processor 服务的。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
通过停止 order-processor 服务来模拟故障。一旦实例停止,来自 checkout 服务的服务调用操作将开始失败。
由于 resiliency.yaml 规范将 order-processor 服务定义为弹性目标,所有失败的请求都将应用重试和断路器策略:
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
在 order-processor 窗口中,停止服务:
CMD + C
CTRL + C
一旦第一个请求失败,将应用名为 retryForever 的重试策略:
INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...
对于每个失败的请求,重试将以 5 秒的间隔无限期继续。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,断路器策略 simpleCB 将被触发,断路器打开,停止所有请求:
INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
超过 5 秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳闸回到打开状态。
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
重新启动 order-processor 服务后,应用程序将无缝恢复,从中断的地方继续。
在 order-processor 服务终端中,重新启动应用程序:
dapr run --app-port 7001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- dotnet run
checkout 服务输出:
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门目录的根目录导航到 order-processor 目录。
cd service_invocation/java/http/order-processor
安装依赖:
mvn clean install
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-id order-processor --resources-path ../../../resources/ --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
checkout 服务应用程序在新的终端窗口中,从快速入门目录的根目录导航到 checkout 目录。
cd service_invocation/java/http/checkout
安装依赖:
mvn clean install
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
然后 Dapr 边车将加载位于 resources 目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
当两个服务和边车都在运行时,注意订单是如何使用 Dapr 服务调用从 checkout 服务传递到 order-processor 服务的。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
通过停止 order-processor 服务来模拟故障。一旦实例停止,来自 checkout 服务的服务调用操作将开始失败。
由于 resiliency.yaml 规范将 order-processor 服务定义为弹性目标,所有失败的请求都将应用重试和断路器策略:
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
在 order-processor 窗口中,停止服务:
CMD + C
CTRL + C
一旦第一个请求失败,将应用名为 retryForever 的重试策略:
INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...
对于每个失败的请求,重试将以 5 秒的间隔无限期继续。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,断路器策略 simpleCB 将被触发,断路器打开,停止所有请求:
INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
超过 5 秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳闸回到打开状态。
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
重新启动 order-processor 服务后,应用程序将无缝恢复,从中断的地方继续。
在 order-processor 服务终端中,重新启动应用程序:
dapr run --app-id order-processor --resources-path ../../../resources/ --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
checkout 服务输出:
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
对于此示例,您需要:
git clone https://github.com/dapr/quickstarts.git
order-processor 服务在终端窗口中,从快速入门目录的根目录导航到 order-processor 目录。
cd service_invocation/go/http/order-processor
安装依赖:
go build .
运行 order-processor 服务以及 Dapr 边车。
dapr run --app-port 6001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- go run .
checkout 服务应用程序在新的终端窗口中,从快速入门目录的根目录导航到 checkout 目录。
cd service_invocation/go/http/checkout
安装依赖:
go build .
运行 checkout 服务以及 Dapr 边车。
dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- go run .
然后 Dapr 边车将加载位于 resources 目录中的弹性规范:
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
scopes:
- checkout
spec:
policies:
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
当两个服务和边车都在运行时,注意订单是如何使用 Dapr 服务调用从 checkout 服务传递到 order-processor 服务的。
checkout 服务输出:
== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
order-processor 服务输出:
== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
通过停止 order-processor 服务来模拟故障。一旦实例停止,来自 checkout 服务的服务调用操作将开始失败。
由于 resiliency.yaml 规范将 order-processor 服务定义为弹性目标,所有失败的请求都将应用重试和断路器策略:
targets:
apps:
order-processor:
retry: retryForever
circuitBreaker: simpleCB
在 order-processor 窗口中,停止服务:
CMD + C
CTRL + C
一旦第一个请求失败,将应用名为 retryForever 的重试策略:
INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...
对于每个失败的请求,重试将以 5 秒的间隔无限期继续。
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
一旦 5 次连续重试失败,断路器策略 simpleCB 将被触发,断路器打开,停止所有请求:
INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open
circuitBreakers:
simpleCB:
maxRequests: 1
timeout: 5s
trip: consecutiveFailures >= 5
超过 5 秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,电路将跳闸回到打开状态。
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open
只要 Redis 容器停止,这种半开/打开行为就会持续下去。
重新启动 order-processor 服务后,应用程序将无缝恢复,从中断的地方继续。
在 order-processor 服务终端中,重新启动应用程序:
dapr run --app-port 6001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- go run .
checkout 服务输出:
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}
order-processor 服务输出:
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}
我们正在不断努力改进我们的快速入门示例,重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?
加入我们的 discord 频道 进行讨论。
访问 此链接 以获取有关 Dapr 弹性的更多信息。
探索 Dapr 教程 >>