

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用 AWS Step Functions 实施无服务器 saga 模式
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions"></a>

*Tabby Ward、Joe Kern 和 Rohan Mehta，Amazon Web Services*

## Summary
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-summary"></a>

在微服务架构中，主要目标是构建解耦且独立的组件，以提高应用程序的敏捷性、灵活性和更快的上市时间。解耦后，每个微服务组件都配有自己的数据持久层。在分布式架构中，业务事务可跨越多个微服务。由于这些微服务不能使用单个原子性、一致性、隔离性、持久性 (ACID) 事务，最终可能会出现不完全的事务。在这种情况下，需要一些控制逻辑撤销已经处理的事务。分布式 saga 模式通常用于该目的。 

Saga 模式是一种故障管理模式，有助于在分布式应用程序中建立一致性，并协调多个微服务之间的事务以保持数据一致性。当您使用 saga 模式时，每个执行事务的服务都会发布事件，该事件会触发后续服务执行链中的下一个事务。这种情况一直持续至链中的最后一笔事务完成。如果业务事务处理失败，saga 会编排一系列补偿性事务，以撤销先前事务所做的更改。

此模式演示了如何使用 AWS Step Functions、AWS Lambda 和 Amazon DynamoDB 等无服务器技术自动设置和部署示例应用程序（用于处理差旅预订）。示例应用程序还使用 Amazon API Gateway 和 Amazon Simple Notiﬁcation Service (Amazon SNS) 实现 saga 执行协调器。该模式可以通过 AWS Cloud Development Kit (AWS CDK)、AWS Serverless Application Model (AWS SAM) 或 Terraform 等基础设施即代码（IaC）框架进行部署。

## 先决条件和限制
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-prereqs"></a>

**先决条件**
+ 一个活跃的 AWS 账户。
+ 创建 AWS CloudFormation 堆栈的权限。有关更多信息，请参阅 CloudFormation 文档中的[控制访问权限](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html)。
+ 使用您的 Amazon Web Services account 配置您选择的 IaC 框架（AWS CDK、AWS SAM 或 Terraform），以便您可使用框架 CLI 部署应用程序。
+ NodeJS，用于在本地构建和运行应用程序。
+ 您选择的代码编辑器（例如 Visual Studio Code、Sublime 或 Atom）。

**产品版本**
+ [NodeJS 版本 14](https://nodejs.org/en/download/)
+ [AWS CDK 版本 2.37.1](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install)
+ [AWS SAM 版本 1.71.0](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
+ [Terraform 版本 1.3.7](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli)

**限制**

事件源是在微服务架构中实现 saga 编排模式的一种自然方式，在这种架构中，所有组件都是松耦合的，彼此之间没有直接的了解。如果您的事务涉及少量步骤（三到五步），那么 saga 模式可能非常合适。但是，复杂性会随微服务数量和步骤数量的增加而增加。 

使用此类设计时，测试和调试可能会变得困难，因为必须运行所有服务才能模拟事务模式。

## 架构
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-architecture"></a>

**目标架构**

拟议架构使用 AWS Step Functions 构建 saga 模式，用于预订航班、预订租车和处理度假付款。

以下工作流图介绍了旅行预订系统的典型流程。工作流程包括预订航空旅行 (” ReserveFlight “)、预订汽车 (” ReserveCarRental “)、处理付款 (” ProcessPayment “)、确认航班预订 (” ConfirmFlight “) 和确认租车 (” ConfirmCarRental “)，然后在这些步骤完成后发出成功通知。但是，如果系统在运行其中任何一个事务时遇到任何错误，它会开始向后失败。例如，付款处理错误 (” ProcessPayment “) 会触发退款 (” RefundPayment “)，然后退款会触发取消租车和航班（CancelRentalReservation” 和 CancelFlightReservation “”），从而以失败消息结束整个交易。

这种模式为图表中突出显示的每项任务部署了单独的 Lambda 函数，还为航班、汽车租赁和付款部署了三项 DynamoDB 表。每个 Lambda 函数都创建、更新或删除相应的 DynamoDB 表中的行，具体取决于事务是确认还是回滚。该模式使用 Amazon SNS 向订阅用户发送短信 (SMS) 消息，通知其事务失败或成功。 

![基于 Saga 模式的旅行预订系统的工作流。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/images/pattern-img/fec0789c-d9b1-4d80-b179-dd9a7ecbec07/images/daad3e8e-6e6b-41c2-95c1-ca79d53ead64.png)


 

**自动化和扩展**

您可以使用其中一个 IaC 框架为此架构创建配置。通过以下链接之一获取首选 IaC。
+ [使用 AWS CDK 部署](https://serverlessland.com/workflows/saga-pattern-cdk)
+ [使用 AWS SAM 部署](https://serverlessland.com/workflows/saga-pattern-sam)
+ [使用 Terraform 部署](https://serverlessland.com/workflows/saga-pattern-tf)

## 工具
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-tools"></a>

**Amazon Web Services**
+ [AWS Step Functions](https://aws.amazon.com/step-functions/) 是一项无服务器编排服务，可让您搭配使用 AWS Lambda 函数和其他 Amazon Web Services 来构建业务关键型应用程序。通过 Step Functions 图形控制台，您可以将应用程序的工作流视为一系列事件驱动的步骤。
+ [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) 是一种全托管 NoSQL 数据库服务，提供快速而可预测的性能，能够实现无缝扩展。您可以使用 DynamoDB 创建一个数据库表来存储和检索任意量级的数据，并支持任何级别的请求流量。
+ [AWS Lambda](https://aws.amazon.com/lambda/) 是一项计算服务，可帮助您运行代码，无需预置或管理服务器。Lambda 只在需要时运行您的代码，并自动进行扩展，从每天几个请求扩展到每秒数千个请求。
+ A@@ [mazon API Gateway](https://aws.amazon.com/api-gateway/) 是一项 AWS 服务，用于创建、发布、维护、监控和保护任何规模的 RES WebSocket APIs T、HTTP。
+ [Amazon Simple Notiﬁcation Service (Amazon SNS)](https://aws.amazon.com/sns/) 是一项托管服务，提供从发布者至订阅用户的消息传输。
+ [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) 是一个软件开发框架，用于使用熟悉的编程语言（例如，Python TypeScript JavaScript、Java 和 C\#/Net）来定义您的云应用程序资源。
+ [AWS Serverless Application Model (AWS SAM)](https://aws.amazon.com/serverless/sam/) 是一个用于构建无服务器应用程序的开源框架。它提供了用于表达函数 APIs、数据库和事件源映射的速记语法。

**代码**

可以在以下链接中找到演示 saga 模式的示例应用程序的代码，包括 IaC 模板 (AWS CDK、AWS SAM 或 Terraform)、Lambda 函数和 DynamoDB 表。按照首个操作说明中的说明安装这些工具。
+ [使用 AWS CDK 部署](https://serverlessland.com/workflows/saga-pattern-cdk)
+ [使用 AWS SAM 部署](https://serverlessland.com/workflows/saga-pattern-sam)
+ [使用 Terraform 部署](https://serverlessland.com/workflows/saga-pattern-tf)

## 操作说明
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-epics"></a>

### 安装软件包、编译与构建
<a name="install-packages-compile-and-build"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 安装 NPM 软件包。 | 创建一个新目录，在终端中导航到该目录，然后在此模式前面的 “*代码*” 部分中克隆您选择的 GitHub 存储库。<br />在包含 `package.json` 文件的根文件夹中，运行以下命令下载并安装所有 Node Package Manager (NPM) 软件包：<pre>npm install</pre> | 开发人员、云架构师 | 
| 编译脚本。 | 在根文件夹中，运行以下命令以指示 TypeScript 转译器创建所有必需 JavaScript 的文件：<pre>npm run build</pre> | 开发人员、云架构师 | 
| 注意更改和重新编译。 | 在根文件夹，在单独的终端窗口中运行以下命令，以监视代码更改，并在检测到更改时编译代码：<pre>npm run watch</pre> | 开发人员、云架构师 | 
| 运行单元测试（仅限 AWS CDK）。 | 如果您使用的是 AWS CDK，请在根文件夹中运行以下命令，以执行 Jest 单元测试：<pre>npm run test</pre> | 开发人员、云架构师 | 

### 将资源部署至目标 Amazon Web Services account
<a name="deploy-resources-to-the-target-aws-account"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 将演示堆栈部署至 AWS。 | 该应用程序与 AWS 区域无关。如果您使用配置文件，则必须在 [AWS 命令行界面（AWS CLI）配置文件](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)中或通过 [AWS CLI 环境变量](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html)明确声明区域。在根文件夹中，运行以下命令，以创建部署程序集并将其部署至默认 Amazon Web Services account 和区域。<br />AWS CDK：<pre>cdk bootstrap<br />cdk deploy</pre><br />AWS SAM：<pre>sam build<br />sam deploy --guided</pre><br />Terraform：<pre>terraform init<br />terraform apply</pre><br />此步骤可能需要几分钟时间才能完成。此命令使用为 AWS CLI 配置的默认凭证。<br />记下部署完成后控制台上所示 API Gateway 网址。您将需要这些信息测试 saga 的执行流程。 | 开发人员、云架构师 | 
| 将已部署的堆栈与当前状态比较。 | 在根文件夹中，运行以下命令，将已部署的堆栈与更改源代码后的当前状态比较：<br />AWS CDK：<pre>cdk diff</pre><br />AWS SAM：<pre>sam deploy</pre><br />Terraform：<pre>terraform plan</pre> | 开发人员、云架构师 | 

### 测试执行流程
<a name="test-the-execution-flow"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 测试 saga 执行流程。 | 导航至您在部署堆栈时在前面的步骤中记下的 API Gateway 网址。此 URL 将触发状态机启动。有关如何通过传递不同的 URL 参数操纵状态机流程的更多信息，请参阅[其他信息](#implement-the-serverless-saga-pattern-by-using-aws-step-functions-additional)部分。<br />要查看结果，请登录 AWS 管理控制台 并导航到 Step Functions 控制台。在这里，您可以看到 saga 状态机的每一个步骤。您还可以查看 DynamoDB 表以查看已插入、更新或删除记录。如果您经常刷新屏幕，则可以看到事务状态从 `pending` 变为 `confirmed`。 <br />您可以通过使用您的手机号码更新 `stateMachine.ts` 文件中的代码，以订阅 SNS 主题，以便在预订成功或失败时接收 SMS 消息。有关更多信息，请参阅[其他信息](#implement-the-serverless-saga-pattern-by-using-aws-step-functions-additional)部分中的 *Amazon SNS*。 | 开发人员、云架构师 | 

### 清理
<a name="clean-up"></a>


| Task | 说明 | 所需技能 | 
| --- | --- | --- | 
| 清理资源。 | 若要清理为此应用程序部署的资源，可以使用以下命令之一。<br />AWS CDK：<pre>cdk destroy</pre><br />AWS SAM：<pre>sam delete</pre><br />Terraform：<pre>terraform destroy</pre> | 应用程序开发人员、云架构师 | 

## 相关资源
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-resources"></a>

**技术论文**
+ [在 AWS 上实施微服务](https://docs.aws.amazon.com/pdfs/whitepapers/latest/microservices-on-aws/microservices-on-aws.pdf)
+ [无服务器应用程序剖析](https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/welcome.html)

**Amazon Web Services 文档**
+ [AWS CDK 入门](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html)
+ [AWS SAM 入门](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started.html)
+ [AWS Step Functions](https://docs.aws.amazon.com/step-functions/)
+ [Amazon DynamoDB](https://docs.aws.amazon.com/dynamodb/)
+ [AWS Lambda](https://docs.aws.amazon.com/lambda/)
+ [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/)
+ [Amazon SNS](https://docs.aws.amazon.com/sns/)

**教程**
+ [无服务器计算实践研讨会](https://aws.amazon.com/serverless-workshops/)

## 附加信息
<a name="implement-the-serverless-saga-pattern-by-using-aws-step-functions-additional"></a>

**代码**

出于测试目的，此模式部署了 API Gateway 和用于触发 Step Functions 状态机的测试 Lambda 函数。使用 Step Functions，您可以通过传递`run_type`参数来模仿 “、”、“” ReserveFlight、“” 和 “” ReserveCarRental 和 “” 中的故障ProcessPayment，从而控制旅行预订系统的功能ConfirmCarRental。ConfirmFlight

`saga` Lambda 函数 (`sagaLambda.ts`) 从 API Gateway 网址中的查询参数中获取输入，创建以下 JSON 对象，然后将其传递至 Step Functions 执行：

```
let input = {
"trip_id": tripID, //  value taken from query parameter, default is AWS request ID
"depart_city": "Detroit",
"depart_time": "2021-07-07T06:00:00.000Z",
"arrive_city": "Frankfurt",
"arrive_time": "2021-07-09T08:00:00.000Z",
"rental": "BMW",
"rental_from": "2021-07-09T00:00:00.000Z",
"rental_to": "2021-07-17T00:00:00.000Z",
"run_type": runType // value taken from query parameter, default is "success"
};
```

您可通过传递以下 URL 参数试验 Step Functions 状态机的不同流程：
+ **成功执行** ─ https://{api gateway url}
+ **预订航班失败** ─ https://{api gateway url}？ **runType= failFlightsReservation**
+ **确认飞行失败** ─ https://{api gateway url}？ **runType= failFlightsConfirmation**
+ **预订租车失败** ─ https://{api gateway url}？ **runType= 预留 failCarRental**
+ **确认租车失败** ─ https://{api gateway url}？ **runType= 确认 failCarRental**
+ **处理付款失败** ─ https://{api gateway url}?**runType=failPayment**
+ **传递行程 ID** ─ https://{api gateway url}?**tripID=**{默认情况下，行程 ID 是 AWS 请求 ID}

**IaC 模板**

链接的存储库包含 IaC 模板，您可使用这些模板来创建整个示例旅行预订应用程序。
+ [使用 AWS CDK 部署](https://serverlessland.com/workflows/saga-pattern-cdk)
+ [使用 AWS SAM 部署](https://serverlessland.com/workflows/saga-pattern-sam)
+ [使用 Terraform 部署](https://serverlessland.com/workflows/saga-pattern-tf)

**DynamoDB 表**

以下是关于航班、租车和付款表的数据模型。

```
Flight Data Model:
 var params = {
      TableName: process.env.TABLE_NAME,
      Item: {
        'pk' : {S: event.trip_id},
        'sk' : {S: flightReservationID},
        'trip_id' : {S: event.trip_id},
        'id': {S: flightReservationID},
        'depart_city' : {S: event.depart_city},
        'depart_time': {S: event.depart_time},
        'arrive_city': {S: event.arrive_city},
        'arrive_time': {S: event.arrive_time},
        'transaction_status': {S: 'pending'}
      }
    };

Car Rental Data Model:
var params = {
      TableName: process.env.TABLE_NAME,
      Item: {
        'pk' : {S: event.trip_id},
        'sk' : {S: carRentalReservationID},
        'trip_id' : {S: event.trip_id},
        'id': {S: carRentalReservationID},
        'rental': {S: event.rental},
        'rental_from': {S: event.rental_from},
        'rental_to': {S: event.rental_to},
        'transaction_status': {S: 'pending'}
      }
    };

Payment Data Model:
var params = {
      TableName: process.env.TABLE_NAME,
      Item: {
        'pk' : {S: event.trip_id},
        'sk' : {S: paymentID},
        'trip_id' : {S: event.trip_id},
        'id': {S: paymentID},
        'amount': {S: "750.00"}, // hard coded for simplicity as implementing any monetary transaction functionality is beyond the scope of this pattern
        'currency': {S: "USD"},
        'transaction_status': {S: "confirmed"}
      }
    };
```

**Lambda 函数**

将创建以下函数，以支持 Step Functions 中的状态机流程和执行：
+ **预订航班**：在 DynamoDB 航班表中插入一条带有 `pending` 的 `transaction_status` 的记录以预订航班。
+ **确认航班**：更新 DynamoDB 航班表中的记录，将 `transaction_status` 设置为 `confirmed`，以确认航班。
+ **取消航班预订**：从 DynamoDB 航班表内删除记录，以取消待处理的航班。
+ **预订租车**：在 Dynamo CarRentals DB 表中插入一条带有 `transaction_status` a `pending` 的记录以预订租车。
+ **确认租车**：更新 Dynamo CarRentals DB 表中的记录，将其`transaction_status`设置为，`confirmed`以确认租车。
+ **取消租车预订：**从 Dynamo CarRentals DB 表中删除记录，以取消待处理的租车。
+ **处理付款**：在 DynamoDB 付款表中插入付款记录。
+ **取消付款**：从 DynamoDB 付款表删除付款记录。

**Amazon SNS**

该示例应用程序创建了以下主题和订阅，用于发送 SMS 消息，并通知客户预订成功或失败。如果您想在测试示例应用程序时接收短信，请在状态机定义文件中使用有效电话号码更新 SMS 订阅。

AWS CDK 片段（在以下代码的第二行中添加电话号码）：

```
const topic = new  sns.Topic(this, 'Topic');
topic.addSubscription(new subscriptions.SmsSubscription('+11111111111'));
const snsNotificationFailure = new tasks.SnsPublish(this ,'SendingSMSFailure', {
topic:topic,
integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE,
message: sfn.TaskInput.fromText('Your Travel Reservation Failed'),
});
 
const snsNotificationSuccess = new tasks.SnsPublish(this ,'SendingSMSSuccess', {
topic:topic,
integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE,
message: sfn.TaskInput.fromText('Your Travel Reservation is Successful'),
});
```

AWS SAM 片段（用您的有效电话号码替换 `+1111111111` 字符串）：

```
  StateMachineTopic11111111111:
    Type: 'AWS::SNS::Subscription'
    Properties:
      Protocol: sms
      TopicArn:
        Ref: StateMachineTopic
      Endpoint: '+11111111111'
    Metadata:
      'aws:sam:path': SamServerlessSagaStack/StateMachine/Topic/+11111111111/Resource
```

Terraform 片段（用您的有效电话号码替换 `+111111111` 字符串）：

```
resource "aws_sns_topic_subscription" "sms-target" {
  topic_arn = aws_sns_topic.topic.arn
  protocol  = "sms"
  endpoint  = "+11111111111"
}
```

**成功预订**

以下流程说明了成功的预约ReserveFlight，其中 “” ReserveCarRental、“” 和 “ProcessPayment” 后面是 “ConfirmFlight” 和 “” ConfirmCarRental。 通过发送给 SNS 主题订阅者的短信通知客户预订成功。

![使用 Saga 模式通过 Step Functions 实施的成功预留的示例。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/images/pattern-img/fec0789c-d9b1-4d80-b179-dd9a7ecbec07/images/f58c894e-7721-4bc7-8f7d-29f23faa5dc1.png)


**预订失败**

此流程是 saga 模式失败的一个例子。如果在预订航班和租车后，“ProcessPayment” 失败，则按相反的顺序取消步骤。 预订已解除，并通过发送至 SNS 主题订阅用户的 SMS 消息通知客户失败。

![使用 Saga 模式通过 Step Functions 实施的失败预留的示例。](http://docs.aws.amazon.com/zh_cn/prescriptive-guidance/latest/patterns/images/pattern-img/fec0789c-d9b1-4d80-b179-dd9a7ecbec07/images/7c64d326-be27-42c3-b03f-d677efedb9a7.png)
