This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Dapr 快速入门

通过旨在帮助您快速上手 Dapr 的代码示例,尝试 Dapr 快速入门

通过我们的 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)交互。

1 - 快速入门:服务调用

开始使用 Dapr 的服务调用构建块

借助 Dapr 的服务调用构建块,您的应用程序可以与其他应用程序进行可靠、安全的通信。

显示服务调用步骤的图表

Dapr 为服务调用提供了多种方法,您可以根据场景选择。对于本快速入门,您将使 checkout 服务能够使用 HTTP 代理调用 order-processor 服务中的方法,并通过以下方式之一:

概述文章中了解更多关于 Dapr 服务调用的方法。

使用多应用运行

在继续快速入门之前,请选择您的首选语言。

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/python/http

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
pip3 install -r requirements.txt
cd ../checkout
pip3 install -r requirements.txt
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 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
)

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/javascript/http

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
npm install
cd ../checkout
npm install
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 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);

步骤 1:前提条件

对于本示例,您需要:

注意:.NET 6 是本版本中 Dapr .NET SDK 包支持的最低 .NET 版本。从 Dapr v1.16 及更高版本开始,将仅支持 .NET 8 和 .NET 9。

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/csharp/http

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 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);

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/java/http

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
mvn clean install
cd ../checkout
mvn clean install
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 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)

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/go/http

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
go build .
cd ../checkout
go build .
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 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)

一次运行一个应用程序

在继续快速入门之前,请选择您的首选语言。

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 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)

步骤 4:运行 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
)

步骤 5:使用多应用运行

您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

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}

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 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);
});

步骤 4:运行 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);

步骤 5:使用多应用运行

您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

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}

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 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();
});

步骤 4:运行 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);

步骤 5:使用多应用运行

您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

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 }

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 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();
    }

步骤 4:运行 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)

步骤 5:使用多应用运行

您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

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

步骤 1:前提条件

对于本示例,您需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 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))
}

步骤 4:运行 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)

步骤 5:使用多应用运行

您可以使用多应用运行模板运行本快速入门中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,而是运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

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 教程 >>

2 - 快速入门:发布订阅

Dapr 发布订阅构建块入门

让我们来了解一下 Dapr 的[发布订阅构建块](https://docs.dapr.io/zh-hans/developing-applications/building-blocks/pubsub/)。在本快速入门中,您将运行一个发布者微服务和一个订阅者微服务,以演示 Dapr 如何实现发布订阅模式。

  1. 使用发布者服务,开发者可以向主题重复发布消息。
  2. [发布订阅组件](https://docs.dapr.io/concepts/components-concept/#pubsub-brokers)对这些消息进行排队或代理。下面的示例使用 Redis,您也可以使用 RabbitMQ、Kafka 等。
  3. 该主题的订阅者从队列中拉取消息并对其进行处理。

您可以通过以下方式尝试此发布订阅快速入门:

使用多应用运行

在继续快速入门之前,请选择您偏好的语言特定的 Dapr SDK。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录导航到发布/订阅目录:

cd pub_sub/python/sdk

order-processorcheckout 应用程序安装依赖项:

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 ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 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',
    )

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录导航到发布/订阅目录:

cd pub_sub/javascript/sdk

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
npm install
cd ..
cd ./checkout
npm install
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 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));

步骤 1:先决条件

对于此示例,您需要:

注意:.NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。Dapr v1.16 及更高版本将仅支持 .NET 8 和 .NET 9。

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录导航到发布/订阅目录:

cd pub_sub/csharp/sdk

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 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);

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录导航到发布/订阅目录:

cd pub_sub/java/sdk

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
mvn clean install
cd ..
cd ./checkout
mvn clean install
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 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());

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录导航到发布/订阅目录:

cd pub_sub/go/sdk

order-processorcheckout 应用程序安装依赖项:

cd ./order-processor
go build .
cd ../checkout
go build .
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 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。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 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)

步骤 4:发布主题

在新的终端窗口中,导航到 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',
    )

步骤 5:查看发布订阅输出

发布者向 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 容器,位于:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.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 指定哪些应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/javascript/sdk/order-processor

安装依赖项,这将包括来自 JavaScript SDK 的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-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)));

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd pub_sub/javascript/sdk/checkout

安装依赖项,这将包括来自 JavaScript SDK 的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-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));

步骤 5:查看发布订阅输出

请注意,如上面的代码中所指定,发布者向 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 容器,位于:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.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 指定哪些应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 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);

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 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);

步骤 5:查看发布订阅输出

请注意,如上面的代码中所指定,发布者向 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 容器,位于:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.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 指定哪些应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 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);
        }
    });
}

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 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());

步骤 5:查看发布订阅输出

请注意,如上面的代码中所指定,发布者向 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 容器,位于:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.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 指定哪些应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 快速入门存储库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 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
}

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 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)

步骤 5:查看发布订阅输出

请注意,如上面的代码中所指定,发布者向 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 容器,位于:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.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 频道 进行讨论。

后续步骤

  • 使用 HTTP 而不是 SDK 来设置发布订阅。
  • 了解有关[作为 Dapr 构建块的发布订阅](https://docs.dapr.io/zh-hans/developing-applications/building-blocks/pubsub/pubsub-overview/)的更多信息
探索 Dapr 教程 >>

3 - 快速入门:工作流

Dapr 工作流构建块入门

让我们来了解一下 Dapr 工作流构建块。在本快速入门中,你将创建一个简单的控制台应用程序,演示 Dapr 的工作流编程模型和工作流管理 API。

在本指南中,你将:

  • 运行 order-processor 应用程序。
  • 启动工作流并观察工作流活动/任务的执行。
  • 查看工作流逻辑以及工作流活动及其在代码中的表示方式。

该工作流包含以下活动:

  • NotifyActivity:使用记录器在工作流过程中输出消息。
  • VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。
  • RequestApprovalActivity:请求对超过特定成本阈值的订单进行批准。
  • ProcessPaymentActivity:处理并授权付款。
  • UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。

该工作流还包含业务逻辑:

  • 如果库存不足,工作流将不会继续进行付款。
  • 当订单总成本大于 5000 时,工作流将调用 RequestApprovalActivity 并等待外部批准事件。
  • 如果订单未获得批准或批准超时,工作流将不会继续进行付款。

在继续快速入门之前,请选择你首选的语言特定 Dapr SDK。

order-processor 控制台应用程序启动并管理 order_processing_workflow,它模拟从商店购买商品。该工作流由五个独特的工作流活动或任务组成:

  • notify_activity:使用记录器在工作流过程中输出消息。这些消息会在以下情况下通知你:
    • 你的库存不足
    • 你的付款无法处理等
  • verify_inventory_activity:检查状态存储以确保有足够的库存可供购买。
  • request_approval_activity:请求对超过特定成本阈值的订单进行批准。
  • process_payment_activity:处理并授权付款。
  • update_inventory_activity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

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 ..

步骤 3:运行订单处理应用程序

在终端中,使用 多应用运行 启动订单处理应用程序和 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}

(可选)步骤 4:在 Zipkin 中查看

运行 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 . 时:

  1. 创建了一个包含一辆汽车的 OrderPayload。
  2. 为工作流生成了一个唯一的订单 ID(在上述示例中为 fc8a507e4a2246d2917d3ad4e3111240)并调度了工作流。
  3. notify_activity 工作流活动发送一条通知,说明已收到一辆汽车的订单。
  4. verify_inventory_activity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。
  5. 订单总成本为 5000,因此工作流不会调用 request_approval_activity 活动。
  6. process_payment_activity 工作流活动开始处理订单 fc8a507e4a2246d2917d3ad4e3111240 的付款并确认是否成功。
  7. update_inventory_activity 工作流活动在订单处理后使用当前可用的汽车更新库存。
  8. notify_activity 工作流活动发送一条通知,说明订单 fc8a507e4a2246d2917d3ad4e3111240 已完成。
  9. 工作流以完成状态终止,并且 OrderResult 设置为已处理。

order-processor/app.py

在应用程序的程序文件中:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的活动
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:使用新的剩余库存值更新状态存储。

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 order-processor 目录:

cd workflows/javascript/sdk/order-processor

安装依赖项:

cd ./javascript/sdk
npm install
npm run build

步骤 3:运行订单处理应用程序

在终端中,使用 多应用运行 启动订单处理应用程序和 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}

(可选)步骤 4:在 Zipkin 中查看

运行 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 . 时:

  1. 为工作流生成了一个唯一的订单 ID(在上述示例中为 f5087775-779c-4e73-ac77-08edfcb375f4)并调度了工作流。
  2. notifyActivity 工作流活动发送一条通知,说明已收到 1 辆汽车的订单。
  3. verifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。
  4. 你的工作流启动并通知你其状态。
  5. requestApprovalActivity 工作流活动请求对订单 f5087775-779c-4e73-ac77-08edfcb375f4 的批准
  6. processPaymentActivity 工作流活动开始处理订单 f5087775-779c-4e73-ac77-08edfcb375f4 的付款并确认是否成功。
  7. updateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。
  8. notifyActivity 工作流活动发送一条通知,说明订单 f5087775-779c-4e73-ac77-08edfcb375f4 已完成并已处理。
  9. 工作流以完成和已处理状态终止。

order-processor/app.ts

在应用程序文件中:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的活动
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:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。

步骤 1:先决条件

对于此示例,你需要:

注意:.NET 7 是 Dapr v1.15 中 Dapr.Workflows 支持的最低 .NET 版本。在 Dapr v1.16 及更高版本中,仅支持 .NET 8 和 .NET 9。

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 order-processor 目录:

cd workflows/csharp/sdk/order-processor

安装依赖项:

dotnet restore
dotnet build

返回到 csharp/sdk 目录:

cd ..

步骤 3:运行订单处理应用程序

在终端中,使用 多应用运行 启动订单处理应用程序和 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

(可选)步骤 4:在 Zipkin 中查看

运行 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 . 时:

  1. 创建了一个包含一辆汽车的 OrderPayload。
  2. 为工作流生成了一个唯一的订单 ID(在上述示例中为 571a6e25)并调度了工作流。
  3. NotifyActivity 工作流活动发送一条通知,说明已收到一辆汽车的订单。
  4. VerifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。
  5. 订单总成本为 5000,因此工作流不会调用 RequestApprovalActivity 活动。
  6. ProcessPaymentActivity 工作流活动开始处理订单 571a6e25 的付款并确认是否成功。
  7. UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。
  8. NotifyActivity 工作流活动发送一条通知,说明订单 571a6e25 已完成。
  9. 工作流以完成状态终止,并且 OrderResult 设置为已处理。

order-processor/Program.cs

在应用程序的程序文件中:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的活动
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.cs
  • VerifyInventoryActivity.cs
  • RequestApprovalActivity.cs
  • ProcessPaymentActivity.cs
  • UpdateInventoryActivity.cs

观看演示

观看 此视频以演示 Dapr Workflow .NET 演示

order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。该工作流由四个工作流活动或任务组成:

  • NotifyActivity:使用记录器在工作流过程中输出消息。
  • RequestApprovalActivity:请求对超过特定成本阈值的订单进行批准。
  • VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。
  • ProcessPaymentActivity:处理并授权付款。
  • UpdateInventoryActivity:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

导航到 order-processor 目录:

cd workflows/java/sdk/order-processor

安装依赖项:

mvn clean install

返回到 java/sdk 目录:

cd ..

步骤 3:运行订单处理应用程序

在终端中,使用 多应用运行 启动订单处理应用程序和 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}

(可选)步骤 4:在 Zipkin 中查看

运行 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 . 时:

  1. 创建了一个包含一辆汽车的 OrderPayload。
  2. 为工作流生成了一个唯一的订单 ID(在上述示例中为 d1bf548b-c854-44af-978e-90c61ed88e3c)并调度了工作流。
  3. NotifyActivity 工作流活动发送一条通知,说明已收到一辆汽车的订单。
  4. VertifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。库存充足,因此工作流继续进行。
  5. 订单总成本为 5000,因此工作流不会调用 RequestApprovalActivity 活动。
  6. ProcessPaymentActivity 工作流活动开始处理订单 d1bf548b-c854-44af-978e-90c61ed88e3c 的付款并确认是否成功。
  7. UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。
  8. NotifyActivity 工作流活动发送一条通知,说明订单 d1bf548b-c854-44af-978e-90c61ed88e3c 已完成。
  9. 工作流以完成状态终止,并且 orderResult 设置为已处理。

order-processor/WorkflowConsoleApp.java

在应用程序的程序文件中:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的活动
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:从状态存储中移除所请求的项目,并使用新的剩余库存值更新存储。

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 sdk 目录:

cd workflows/go/sdk

步骤 3:运行订单处理应用程序

在终端中,使用 多应用运行 启动订单处理应用程序和 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 .

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当你运行 dapr run 时:

  1. 创建了一个包含一辆汽车的 OrderPayload。
  2. 为工作流生成了一个唯一的订单 ID(在上述示例中为 b4cb2687-1af0-4f8d-9659-eb6389c07ade)并调度了工作流。
  3. NotifyActivity 工作流活动发送一条通知,说明已收到 10 辆汽车的订单。
  4. VerifyInventoryActivity 工作流活动检查库存数据,确定你是否可以供应订购的项目,并响应库存中的汽车数量。
  5. 订单总成本为 5000,因此工作流不会调用 RequestApprovalActivity 活动。
  6. ProcessPaymentActivity 工作流活动开始处理订单 b4cb2687-1af0-4f8d-9659-eb6389c07ade 的付款并确认是否成功。
  7. UpdateInventoryActivity 工作流活动在订单处理后使用当前可用的汽车更新库存。
  8. NotifyActivity 工作流活动发送一条通知,说明订单 b4cb2687-1af0-4f8d-9659-eb6389c07ade 已完成。
  9. 工作流以完成状态终止,并且 OrderResult 设置为已处理。

order-processor/main.go

在应用程序的程序文件中:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的活动
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.go

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

步骤 5:管理工作流

现在你的工作流正在运行,让我们学习如何使用 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"

清理

测试后,清除已完成的工作流。

# 清除特定工作流
dapr workflow purge e4d3807c --app-id order-processor

# 或清除所有已完成的工作流
dapr workflow purge --app-id order-processor

告诉我们你的想法!

我们一直在努力改进快速入门示例,重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?

在我们的 discord 频道 中加入讨论。

后续步骤






探索 Dapr 教程  >>

4 - 快速入门:状态管理

开始使用 Dapr 状态管理构建块

让我们来看看 Dapr 的状态管理构建块。在本快速入门中,您将使用 Redis 状态存储来保存、获取和删除状态,可以通过以下方式:

虽然此示例使用 Redis,但您可以将其替换为任何支持的状态存储

使用多应用运行

在继续快速入门之前,选择您首选的语言特定的 Dapr SDK。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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)

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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)

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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。

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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);

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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);
  }

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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))

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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)

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 order-processor 目录。

cd state_management/javascript/sdk/order-processor

安装依赖项,其中包括 JavaScript SDK 中的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-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)

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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);

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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);
  }

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 实例的连接。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端窗口中,导航到 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))

步骤 3:查看 order-processor 输出

注意,如上面的代码所示,代码将应用程序状态保存在 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 容器,位于:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.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 教程 >>

5 - 快速入门:输入与输出绑定

开始使用 Dapr 的绑定构建块

让我们来看一下 Dapr 的绑定构建块。使用绑定,您可以:

  • 使用来自外部系统的事件触发您的应用程序。
  • 与外部系统进行交互。

在本快速入门中,您使用输入 Cron 绑定安排一个批处理脚本每 10 秒运行一次。该脚本处理一个 JSON 文件,并使用 PostgreSQL Dapr 绑定将数据输出到 SQL 数据库。

在继续快速入门之前,请选择您偏好的特定语言的 Dapr SDK。

前置条件

对于此示例,您需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

第 2 步:在本地运行 PostgreSQL Docker 容器

在您的机器上的 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

第 3 步:安排 Cron 作业并写入数据库

在新的终端窗口中,导航到 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 输出绑定,将 OrderIdCustomerPrice 记录插入到 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)

第 4 步:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 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 边车:

为此快速入门包含的 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.yamlmetadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 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 实例的连接。

前置条件

对于此示例,您需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

第 2 步:在本地运行 PostgreSQL Docker 容器

在您的机器上的 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

第 3 步:安排 Cron 作业并写入数据库

在新的终端窗口中,导航到 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 输出绑定,将 OrderIdCustomerPrice 记录插入到 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;
}

第 4 步:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 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 边车:

为此快速入门包含的 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.yamlmetadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 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。

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

第 2 步:在本地运行 PostgreSQL Docker 容器

在您的机器上的 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

第 3 步:安排 Cron 作业并写入数据库

在新的终端窗口中,导航到 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 输出绑定,将 OrderIdCustomerPrice 记录插入到 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);

第 4 步:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 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 边车:

为此快速入门包含的 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.yamlmetadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 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 实例的连接。

前置条件

对于此示例,您需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

第 2 步:在本地运行 PostgreSQL Docker 容器

在您的机器上的 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

第 3 步:安排 Cron 作业并写入数据库

在新的终端窗口中,导航到 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 输出绑定,将 OrderIdCustomerPrice 记录插入到 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");
}

第 4 步:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 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 边车:

为此快速入门包含的 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.yamlmetadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 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 实例的连接。

前置条件

对于此示例,您需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

第 2 步:在本地运行 PostgreSQL Docker 容器

在您的机器上的 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

第 3 步:安排 Cron 作业并写入数据库

在新的终端窗口中,导航到 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 输出绑定,将 OrderIdCustomerPrice 记录插入到 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
}

第 4 步:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 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 边车:

为此快速入门包含的 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.yamlmetadata 部分包含一个 Cron 表达式,用于指定调用绑定的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 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 教程 >>

6 - 快速入门:Actors

开始使用 Dapr 的 Actors 构建块

让我们来看看 Dapr 的 Actors 构建块。在此快速入门中,你将运行一个智能设备微服务和一个简单的控制台客户端,以演示 Dapr Actors 中的有状态对象模式。

目前,你可以使用 .NET SDK 体验此 actors 快速入门。

作为 .NET actors 快速入门的简要概述:

  1. 使用 SmartDevice.Service 微服务,你托管:
    • 两个 SmokeDetectorActor 烟雾报警对象
    • 一个 ControllerActor 对象,用于命令和控制智能设备
  2. 使用 SmartDevice.Client 控制台应用,客户端应用与每个 actor 或控制器交互,以总体执行操作。
  3. SmartDevice.Interfaces 包含服务应用和客户端应用使用的共享接口和数据类型。

前置条件

对于此示例,你需要:

注意: .NET 6 是此版本中 Dapr .NET SDK 包支持的最低 .NET 版本。Dapr v1.16 及更高版本将仅支持 .NET 8 和 .NET 9。

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行服务应用

在新的终端窗口中,导航到 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

步骤 3:运行客户端应用

在新的终端实例中,导航到 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

(可选)步骤 4:在 Zipkin 中查看

如果你在本地计算机上为 Dapr 配置了 Zipkin,则可以在 Zipkin Web UI(通常位于 http://localhost:9411/zipkin/)中查看 actor 与客户端的交互。

发生了什么?

当你运行客户端应用时,发生了几件事:

  1. 两个 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}");
    
  2. 调用 SmokeDetectorActor 1DetectSmokeAsync 方法

     public async Task DetectSmokeAsync()
     {
         var controllerActorId = new ActorId("controller");
         var controllerActorType = "ControllerActor";
         var controllerProxy = ProxyFactory.CreateActorProxy<IController>(controllerActorId, controllerActorType);
         await controllerProxy.TriggerAlarmForAllDetectors();
     }
    
  3. 调用 ControllerActorTriggerAlarmForAllDetectors 方法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();   
    
  4. 调用 SmokeDetectorActor 12SoundAlarm 方法

    storedDeviceData1 = await proxySmartDevice1.GetDataAsync();
    Console.WriteLine($"Device 1 state: {storedDeviceData1}");
    storedDeviceData2 = await proxySmartDevice2.GetDataAsync();
    Console.WriteLine($"Device 2 state: {storedDeviceData2}");
    
  5. 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));
    

有关示例的完整上下文,请查看以下代码:

告诉我们你的想法!

我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?

加入我们的 discord 频道 参与讨论。

后续步骤

了解有关 Actor 构建块 的更多信息

探索 Dapr 教程 >>

7 - 快速入门:机密管理

开始使用 Dapr 的机密管理构建块

Dapr 提供了一个专门的机密 API,允许开发者从机密存储中检索机密。在本快速入门中,你将:

  1. 运行带有机密存储组件的微服务。
  2. 在应用程序代码中使用 Dapr 机密 API 检索机密。
显示示例服务机密管理的图表。

在继续快速入门之前,请选择你喜欢的语言特定的 Dapr SDK。

前提条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:检索机密

在终端窗口中,导航到 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_STORElocal-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_NAMEsecrets.json 文件中定义,位于 secrets_management/python/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤 3:查看 order-processor 输出

如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。

Order-processor 输出:

== APP == INFO:root:Fetched Secret: {'secret': 'YourPasskeyHere'}

前提条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:检索机密

在终端窗口中,导航到 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_STORElocal-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_NAMEsecrets.json 文件中定义,位于 secrets_management/javascript/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤 3:查看 order-processor 输出

如上面的应用程序代码所指定,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。

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:检索机密

在终端窗口中,导航到 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_STORElocal-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_NAMEsecrets.json 文件中定义,位于 secrets_management/csharp/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤 3:查看 order-processor 输出

如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。

Order-processor 输出:

== APP == Fetched Secret: [secret, YourPasskeyHere]

前提条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:检索机密

在终端窗口中,导航到 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_STORElocal-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_NAMEsecrets.json 文件中定义,位于 secrets_management/java/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤 3:查看 order-processor 输出

如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。

Order-processor 输出:

== APP == Fetched Secret: {secret=YourPasskeyHere}

前提条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:检索机密

在终端窗口中,导航到 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_STORElocal-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_NAMEsecrets.json 文件中定义,位于 secrets_management/go/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤 3:查看 order-processor 输出

如上面的应用程序代码所指定,order-processor 服务通过 Dapr 机密存储检索机密并在控制台中显示它。

Order-processor 输出:

== APP == Fetched Secret:  YourPasskeyHere

告诉我们你的想法!

我们正在不断努力改进我们的快速入门示例,并重视你的反馈。你觉得这个快速入门有帮助吗?你有改进建议吗?

加入我们的 discord 频道 进行讨论。

后续步骤

探索 Dapr 教程 >>

8 - 快速入门:配置

开始使用 Dapr 的配置构建块

让我们来看一下 Dapr 的 配置构建块。配置项通常具有动态特性,与消费它的应用程序需求紧密耦合。配置项是包含配置数据的键/值对,例如:

  • 应用 ID
  • 分区键
  • 数据库名称等

在本快速入门中,你将运行一个使用配置 API 的 order-processor 微服务。该服务:

  1. 从配置存储获取配置项。
  2. 订阅配置更新。
演示使用键/值对的配置 API 快速入门流程的图表。

在继续进行快速入门之前,选择你首选的语言特定的 Dapr SDK。

前置条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 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

(可选)步骤 3:更新配置项值

应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

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 服务包含以下代码:

  • 从配置存储获取配置项
  • 订阅配置更新(你之前在 CLI 中进行的操作)
  • 取消订阅配置更新并在 20 秒不活动后退出应用。

获取配置项:

# 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)

前置条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 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

(可选)步骤 3:更新配置项值

应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

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 服务包含以下代码:

  • 从配置存储获取配置项
  • 订阅配置更新(你之前在 CLI 中进行的操作)
  • 取消订阅配置更新并在 20 秒不活动后退出应用。

获取配置项:

// 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。

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 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

(可选)步骤 3:更新配置项值

应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

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 服务包含以下代码:

  • 从配置存储获取配置项
  • 订阅配置更新(你之前在 CLI 中进行的操作)
  • 取消订阅配置更新并在 20 秒不活动后退出应用。

获取配置项:

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

前置条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 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

(可选)步骤 3:更新配置项值

应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

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 服务包含以下代码:

  • 从配置存储获取配置项
  • 订阅配置更新(你之前在 CLI 中进行的操作)
  • 取消订阅配置更新并在 20 秒不活动后退出应用。

获取配置项:

// 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");
}

前置条件

对于本示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

克隆完成后,打开一个新终端并运行以下命令来为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 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

(可选)步骤 3:更新配置项值

应用取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

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 服务包含以下代码:

  • 从配置存储获取配置项
  • 订阅配置更新(你之前在 CLI 中进行的操作)
  • 取消订阅配置更新并在 20 秒不活动后退出应用。

获取配置项:

// 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 教程 >>

9 - 快速入门:密码学

Dapr 密码学构建块入门

让我们来看看 Dapr 密码学构建块。在这个快速入门中,你将创建一个使用 Dapr 密码学 API 加密和解数据的应用程序。你将:

  • 加密然后解密一个短字符串(使用 RSA 密钥),在 Go 字节切片中读取内存中的结果。
  • 加密然后解密一个大文件(使用 AES 密钥),使用流将加密和解密的数据存储到文件中。

目前,你可以使用 Go SDK 体验密码学 API。

此快速入门包含一个名为 crypto-quickstart 的 JavaScript 应用程序。

前置条件

对于此示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端中,从根目录导航到密码学示例。

cd cryptography/javascript/sdk

导航到包含源代码的文件夹:

cd ./crypto-quickstart

安装依赖项:

npm install

步骤 2:使用 Dapr 运行应用程序

应用程序代码定义了两个必需的密钥:

  • 私有 RSA 密钥
  • 一个 256 位对称(AES)密钥

使用 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);
}
使用 RSA 密钥加密和解密字符串

创建客户端后,应用程序加密一条消息:

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"));

  // ...
}
使用 AES 密钥加密和解密大文件

接下来,应用程序加密一个大的图像文件:

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 应用程序。

前置条件

对于此示例,你需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端中,从根目录导航到密码学示例。

cd cryptography/go/sdk

步骤 2:使用 Dapr 运行应用程序

导航到包含源代码的文件夹:

cd ./crypto-quickstart

应用程序代码定义了两个必需的密钥:

  • 私有 RSA 密钥
  • 一个 256 位对称(AES)密钥

使用 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)
}
使用 RSA 密钥加密和解密字符串

创建客户端后,应用程序加密一条消息:

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))
}
使用 AES 密钥加密和解密大文件

接下来,应用程序加密一个大的图像文件:

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 教程 >>

10 - 快速入门:Jobs

开始使用 Dapr Jobs 构建块

让我们来看看 Dapr Jobs 构建块,它可以安排作业在特定时间或间隔运行。在本快速入门中,您将使用 Dapr 的 Job API 来调度、获取和删除作业。

您可以通过以下方式尝试此 Jobs 快速入门:

使用 Multi-App Run 运行

在继续快速入门之前,选择您首选的特定语言的 Dapr SDK。目前,您可以使用 Go SDK 来试验 Jobs API。

此快速入门包含两个应用程序:

  • job-scheduler.go:调度、检索和删除作业。
  • job-service.go:处理已调度的作业。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 jobs 目录:

cd jobs/go/sdk

步骤 3:调度作业

使用一个命令运行应用程序并调度作业:

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 run -f . 会同时启动 job-schedulerjob-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 应用程序创建服务调用处理程序来管理作业的生命周期(scheduleJobgetJobdeleteJob)。

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 还定义了 schedulegetdelete 函数,从 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:处理已调度的作业。

步骤 1:先决条件

对于此示例,您需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 jobs 目录:

cd jobs/go/sdk

步骤 3:调度作业

在终端中,运行 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 runjob-servicejob-scheduler 应用程序中发生了什么。

观看演示

观看使用 Go HTTP 示例演示 Jobs API 的实际操作,录制于 Dapr Community Call #107

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您是否觉得此快速入门有帮助?您有改进建议吗?

加入我们的 Discord 频道 进行讨论。

后续步骤

探索 Dapr 教程 >>

11 - 快速入门:对话

Dapr 对话构建块入门

让我们来看看 Dapr 对话构建块 如何简化与大语言模型(LLMs)的交互。在这个快速入门中,你使用 echo 组件与模拟 LLM 通信,并要求它定义 Dapr。

你可以通过以下方式尝试此对话快速入门:

使用模板文件运行应用

在继续快速入门之前,请选择你首选的语言特定的 Dapr SDK。

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/python/sdk/conversation

安装依赖项:

pip3 install -r requirements.txt

步骤 3:启动对话服务

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"]

Echo 模拟 LLM 组件

在 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 对话应用

在应用代码中:

  • 应用向 echo 模拟 LLM 组件发送输入 “What is dapr?"。
  • 模拟 LLM 回显 “What is dapr?"。
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}')

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/javascript/http/conversation

安装依赖项:

npm install

步骤 3:启动对话服务

返回到 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"]

Echo 模拟 LLM 组件

在 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 对话应用

在应用代码的第一部分中:

  • 应用向 echo 模拟 LLM 组件发送输入 “What is dapr?"。
  • 模拟 LLM 回显 “What is dapr?"。
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);
}

在应用代码的第二部分中:

  • 应用发送输入 “What is the weather like in San Francisco in celsius”,同时提供可用工具 get_weather 的定义。
  • 模拟 LLM 回显 “What is the weather like in San Francisco in celsius?” 和函数定义,这些内容会在响应中被检测到。
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);

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/csharp/sdk

步骤 3:启动对话服务

使用以下命令启动对话服务:

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"]

Echo 模拟 LLM 组件

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 对话应用

在应用代码中:

  • 应用向 echo 模拟 LLM 组件发送输入 “What is dapr?"。
  • 模拟 LLM 回显 “What is dapr?"。
  • 应用发送输入 “What is the weather like in San Francisco in celsius”,同时提供可用工具 get_weather 的定义。
  • 模拟 LLM 回显 “What is the weather like in San Francisco in celsius?” 和函数定义,这些内容会在响应中被检测到。
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}");

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/java/sdk/conversation

安装依赖项:

mvn clean install

步骤 3:启动对话服务

返回到 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"]

Echo 模拟 LLM 组件

在 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 对话应用

在应用代码中:

  • 应用向 echo 模拟 LLM 组件发送输入 “What is dapr?"。
  • 模拟 LLM 回显 “What is dapr?"。
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);
    }
  }
}

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/go/sdk

步骤 3:启动对话服务

使用以下命令启动对话服务:

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", "."]

Echo 模拟 LLM 组件

在 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 对话应用

在应用代码中:

  • 应用向 echo 模拟 LLM 组件发送输入 “What is dapr?"。
  • 模拟 LLM 回显 “What is dapr?"。
  • 应用发送输入 “What is the weather like in San Francisco in celsius”,同时提供可用工具 get_weather 的定义。
  • 模拟 LLM 回显 “What is the weather like in San Francisco in celsius?” 和函数定义,这些内容会在响应中被检测到。
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)
      }
    }
  }
}

不使用模板运行应用

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/python/sdk/conversation

安装依赖项:

pip3 install -r requirements.txt

步骤 3:启动对话服务

返回到 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?

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/javascript/http/conversation

安装依赖项:

npm install

步骤 3:启动对话服务

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"}}]

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/csharp/sdk/conversation

安装依赖项:

dotnet build

步骤 3:启动对话服务

使用以下命令启动对话服务:

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

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/java/sdk/conversation

安装依赖项:

mvn clean install

步骤 3:启动对话服务

使用以下命令启动对话服务:

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?

步骤 1:先决条件

对于此示例,你需要:

步骤 2:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从 Quickstarts 目录的根目录,导航到 conversation 目录:

cd conversation/go/sdk/conversation

安装依赖项:

go build .

步骤 3:启动对话服务

使用以下命令启动对话服务:

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 教程 >>

12 - 弹性快速入门

开始使用 Dapr 弹性组件

12.1 - 快速入门:服务到组件的弹性

通过状态管理 API 快速上手 Dapr 的弹性能力

通过模拟系统故障来观察 Dapr 的弹性能力。在本快速入门中,你将:

  • 运行一个微服务应用程序,该应用程序通过 Dapr 的状态管理 API 持续持久化和检索状态。
  • 通过模拟系统故障来触发弹性策略。
  • 解决故障后,微服务应用程序将恢复运行。
显示应用于 Dapr API 的弹性的图表

在继续快速入门之前,请选择你偏好的特定语言的 Dapr SDK。

前置条件

对于此示例,你需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到 order-processor 目录。

cd ../state_management/python/sdk/order-processor

安装依赖项

pip3 install -r requirements.txt 

第 2 步:运行应用程序

运行 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' }

第 3 步:引入故障

通过停止在开发机器上执行 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 容器停止,这种半开/打开行为就会持续下去。

第 3 步:移除故障

当你在机器上重新启动 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' }

前置条件

对于此示例,你需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到 order-processor 目录。

cd ../state_management/javascript/sdk/order-processor

安装依赖项

npm install

第 2 步:运行应用程序

运行 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' }

第 3 步:引入故障

通过停止在开发机器上执行 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 容器停止,这种半开/打开行为就会持续下去。

第 3 步:移除故障

当你在机器上重新启动 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' }

前置条件

对于此示例,你需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到 order-processor 目录。

cd ../state_management/csharp/sdk/order-processor

安装依赖项

dotnet restore
dotnet build

第 2 步:运行应用程序

运行 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' }

第 3 步:引入故障

通过停止在开发机器上执行 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 容器停止,这种半开/打开行为就会持续下去。

第 3 步:移除故障

当你在机器上重新启动 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' }

前置条件

对于此示例,你需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到 order-processor 目录。

cd ../state_management/java/sdk/order-processor

安装依赖项

mvn clean install

第 2 步:运行应用程序

运行 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' }

第 3 步:引入故障

通过停止在开发机器上执行 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 容器停止,这种半开/打开行为就会持续下去。

第 3 步:移除故障

当你在机器上重新启动 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' }

前置条件

对于此示例,你需要:

第 1 步:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到 order-processor 目录。

cd ../state_management/go/sdk/order-processor

安装依赖项

go build .

第 2 步:运行应用程序

运行 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' }

第 3 步:引入故障

通过停止在开发机器上执行 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 容器停止,这种半开/打开行为就会持续下去。

第 3 步:移除故障

当你在机器上重新启动 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 教程 >>

12.2 - 快速入门:服务到服务弹性

通过服务调用 API 开始使用 Dapr 的弹性功能

通过模拟系统故障来观察 Dapr 弹性功能。在本快速入门中,您将:

  • 运行两个微服务应用程序:checkoutorder-processorcheckout 将持续向 order-processor 发起 Dapr 服务调用请求。
  • 通过模拟系统故障来触发弹性规范。
  • 移除故障,使微服务应用程序能够恢复。
Diagram showing the resiliency applied to Dapr APIs

在继续快速入门之前,选择您偏好的特定语言的 Dapr SDK。

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行 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

步骤 3:运行 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

步骤 4:查看服务调用输出

当两个服务和边车都在运行时,注意订单是如何使用 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}

步骤 5:引入故障

通过停止 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 服务停止,这种半开/打开行为就会持续下去。

步骤 6:移除故障

重新启动 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}

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行 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

步骤 3:运行 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

步骤 4:查看服务调用输出

当两个服务和边车都在运行时,注意订单是如何使用 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}

步骤 5:引入故障

通过停止 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 容器停止,这种半开/打开行为就会持续下去。

步骤 6:移除故障

重新启动 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}

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行 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

步骤 3:运行 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

步骤 4:查看服务调用输出

当两个服务和边车都在运行时,注意订单是如何使用 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}

步骤 5:引入故障

通过停止 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 容器停止,这种半开/打开行为就会持续下去。

步骤 6:移除故障

重新启动 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}

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行 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

步骤 3:运行 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

步骤 4:查看服务调用输出

当两个服务和边车都在运行时,注意订单是如何使用 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}

步骤 5:引入故障

通过停止 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 容器停止,这种半开/打开行为就会持续下去。

步骤 6:移除故障

重新启动 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}

前置条件

对于此示例,您需要:

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行 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 .

步骤 3:运行 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

步骤 4:查看服务调用输出

当两个服务和边车都在运行时,注意订单是如何使用 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}

步骤 5:引入故障

通过停止 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 容器停止,这种半开/打开行为就会持续下去。

步骤 6:移除故障

重新启动 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 教程 >>