

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 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*

## 摘要
<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 Notification Service (Amazon SNS) 來實作 saga 執行協調器。模式可以使用基礎設施即程式碼 (IaC) 架構進行部署，例如 AWS Cloud Development Kit (AWS CDK)、AWS Serverless Application Model (AWS SAM) 或 Terraform。

## 先決條件和限制
<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)。
+ 使用 AWS 帳戶設定您選擇的 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 模式可能非常適合。不過，複雜度會隨著微服務數量和步驟數量而增加。 

當您使用此設計時，測試和偵錯可能會變得困難，因為您必須執行所有服務才能模擬交易模式。

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

**目標架構 **

提議的架構使用 AWS Step Functions 建置類似模式來預訂航班、預訂租車，以及處理假期的付款。

下列工作流程圖說明行程保留系統的典型流程。工作流程包含預留航空旅程 ("ReserveFlight")、預留車輛 ("ReserveCarRental")、處理付款 ("ProcessPayment")、確認航班保留 ("ConfirmFlight")，以及確認租車 ("ConfirmCarRental")，隨後在這些步驟完成時會收到成功通知。不過，如果系統在執行任何這些交易時遇到任何錯誤，就會開始向後失敗。例如，付款處理 ("ProcessPayment") 的錯誤會觸發退款 ("RefundPayment")，然後觸發租車和航班 ("CancelRentalReservation" 和 "CancelFlightReservation") 的取消，以失敗訊息結束整個交易。

此模式會針對圖表中反白顯示的每個任務部署個別的 Lambda 函數，以及三個用於航班、租車和付款的 DynamoDB 資料表。每個 Lambda 函數都會建立、更新或刪除個別 DynamoDB 資料表中的資料列，視交易是否確認或復原而定。模式使用 Amazon SNS 傳送文字 (SMS) 訊息給訂閱者，通知他們交易失敗或成功。 

![\[根據 saga 模式的旅遊保留系統工作流程。\]](http://docs.aws.amazon.com/zh_tw/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>

**AWS 服務**
+ [AWS Step Functions](https://aws.amazon.com/step-functions/) 是一種無伺服器協同運作服務，可讓您結合 AWS Lambda 函數和其他 AWS 服務來建置業務關鍵應用程式。透過 Step Functions 圖形主控台，您會將應用程式的工作流程視為一系列的事件驅動步驟。
+ [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) 是全受管的 NoSQL 資料庫服務，可提供快速且可預測的效能和無縫的可擴展性。您可以使用 DynamoDB 建立資料庫資料表，藉此存放和擷取任意數量的資料，並為任何層級的請求流量提供服務。
+ [AWS Lambda](https://aws.amazon.com/lambda/) 是一種運算服務，可讓您執行程式碼，而無需佈建或管理伺服器。Lambda 只有在需要時才會執行程式碼，可自動從每天數項請求擴展成每秒數千項請求。
+ [Amazon API Gateway](https://aws.amazon.com/api-gateway/) 是一種 AWS 服務，可用於建立、發佈、維護、監控和保護任何規模的 REST、HTTP 和 WebSocket APIs。
+ [Amazon Simple Notification Service (Amazon SNS)](https://aws.amazon.com/sns/) 是一種受管服務，可將訊息從發佈者交付給訂閱者。
+ [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) 是一種軟體開發架構，可透過使用 TypeScript、JavaScript、Python、Java 和 C\$1/ 等熟悉的程式設計語言來定義您的雲端應用程式資源。淨額。
+ [AWS Serverless Application Model (AWS SAM)](https://aws.amazon.com/serverless/sam/) 是用於建置無伺服器應用程式的開放原始碼架構。它提供速記語法來表達函數、APIs、資料庫和事件來源映射。

**Code**

您可以在以下連結中找到示範 saga 模式的範例應用程式的程式碼，包括 IaC 範本 (AWS CDK、AWS SAM 或 Terraform)、Lambda 函數和 DynamoDB 資料表。請遵循第一個 epic 中的指示來安裝這些項目。
+ [使用 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>


| 任務 | Description | 所需的技能 | 
| --- | --- | --- | 
| 安裝 NPM 套件。 | 建立新的目錄、導覽至終端機中的該目錄，並從此模式稍早的*程式碼*區段複製您選擇的 GitHub 儲存庫。在具有 `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> | 開發人員、雲端架構師 | 

### 將資源部署到目標 AWS 帳戶
<a name="deploy-resources-to-the-target-aws-account"></a>


| 任務 | Description | 所需的技能 | 
| --- | --- | --- | 
| 將示範堆疊部署至 AWS。 | 應用程式與 AWS 區域無關。如果您使用設定檔，則必須在 [AWS Command Line Interface (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)在根資料夾中，執行下列命令來建立部署組件，並將其部署到預設的 AWS 帳戶和區域。AWS CDK：<pre>cdk bootstrap<br />cdk deploy</pre>AWS SAM：<pre>sam build<br />sam deploy --guided</pre>Terraform：<pre>terraform init<br />terraform apply</pre>此步驟可能需要幾分鐘的時間才能完成。此命令使用為 AWS CLI 設定的預設登入資料。請注意，在部署完成後，主控台上顯示的 API Gateway URL。您需要此資訊來測試 saga 執行流程。 | 開發人員、雲端架構師 | 
| 比較已部署的堆疊與目前狀態。 | 在根資料夾中，執行下列命令，在變更原始碼之後，將部署的堆疊與目前狀態 進行比較：AWS CDK：<pre>cdk diff</pre>AWS SAM：<pre>sam deploy</pre>Terraform：<pre>terraform plan</pre> | 開發人員、雲端架構師 | 

### 測試執行流程
<a name="test-the-execution-flow"></a>


| 任務 | Description | 所需的技能 | 
| --- | --- | --- | 
| 測試 saga 執行流程。 | 當您部署堆疊時，導覽至您在先前步驟中記下的 API Gateway URL。此 URL 會觸發狀態機器啟動。如需如何透過傳遞不同的 URL 參數來操作狀態機器流程的詳細資訊，請參閱[其他資訊](#implement-the-serverless-saga-pattern-by-using-aws-step-functions-additional)一節。若要 檢視結果，請登入 AWS 管理主控台，然後導覽至 Step Functions 主控台。在這裡，您可以看到 saga 狀態機器的每個步驟。您也可以檢視 DynamoDB 資料表，以查看插入、更新或刪除的記錄。如果您經常重新整理畫面，您可以觀看交易狀態從 變更為 `pending` `confirmed`。 您可以訂閱 SNS 主題，方法是使用手機號碼更新 `stateMachine.ts` 檔案中的程式碼，以便在成功或失敗的保留時接收簡訊。如需詳細資訊，請參閱[其他資訊](#implement-the-serverless-saga-pattern-by-using-aws-step-functions-additional)區段中的 *Amazon SNS*。 | 開發人員、雲端架構師 | 

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


| 任務 | Description | 所需的技能 | 
| --- | --- | --- | 
| 清除資源。 | 若要清除為此應用程式部署的資源，您可以使用下列其中一個命令。AWS CDK：<pre>cdk destroy</pre>AWS SAM：<pre>sam delete</pre>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)

**AWS 服務文件**
+ [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>

**Code**

為了測試目的，此模式會部署 API Gateway 和測試 Lambda 函數，以觸發 Step Functions 狀態機器。使用 Step Functions，您可以透過傳遞`run_type`參數來模擬 "ReserveFlight"、"ReserveCarRental"、"ProcessPayment"、"ConfirmFlight" 和 "ConfirmCarRental."

`saga` Lambda 函數 (`sagaLambda.ts`) 會從 API Gateway URL 中的查詢參數取得輸入，建立下列 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：//\$1api 閘道 url\$1
+ **預留航班失敗** - https：//\$1api 閘道 url\$1？**runType=failFlightsReservation**
+ **確認航班失敗** - https：//\$1api 閘道 url\$1？**runType=failFlightsConfirmation**
+ **預留租車失敗** - https：//\$1api 閘道 url\$1？**runType=failCarRentalReservation**
+ **確認租車失敗** - https：//\$1api 閘道 url\$1？**runType=failCarRentalConfirmation**
+ **處理付款失敗** - https：//\$1api 閘道 url\$1？**runType=failPayment**
+ **傳遞行程 ID** - https：//\$1api 閘道 url\$1？**tripID=**\$1預設情況下，行程 ID 將是 AWS 請求 ID\$1

**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 中的狀態機器流程和執行：
+ **預留航班**：使用 `transaction_status`的 將記錄插入 DynamoDB 航班資料表`pending`，以預訂航班。
+ **確認航班**：更新 DynamoDB 航班資料表中的記錄，將 `transaction_status`設定為 `confirmed`，以確認航班。
+ **取消航班保留**：從 DynamoDB 航班資料表刪除記錄，以取消待定航班。
+ **預留租車**：使用 `transaction_status`的 將記錄插入 DynamoDB CarRentals 資料表`pending`，以預訂租車。
+ **確認租車**：更新 DynamoDB CarRentals 資料表中的記錄，將 `transaction_status`設定為 `confirmed`，以確認租車。
+ **取消租車保留：**從 DynamoDB CarRentals 資料表刪除記錄，以取消待定的租車。
+ **處理付款**：將記錄插入 DynamoDB 付款資料表以進行付款。
+ **取消付款**：從 DynamoDB 付款資料表中刪除付款的記錄。

**Amazon SNS**

範例應用程式會建立下列主題和訂閱來傳送簡訊，並通知客戶保留成功或失敗。如果您想要在測試範例應用程式時接收文字訊息，請在狀態機器定義檔案中使用有效的電話號碼更新簡訊訂閱。

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 主題訂閱者的簡訊收到成功預訂的通知。

![\[Step Functions 使用 saga 模式成功實作保留的範例。\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/patterns/images/pattern-img/fec0789c-d9b1-4d80-b179-dd9a7ecbec07/images/f58c894e-7721-4bc7-8f7d-29f23faa5dc1.png)


**失敗的保留**

此流程是 saga 模式失敗的範例。如果預訂航班和租車後，「ProcessPayment」失敗，步驟會依相反順序取消。 會釋出保留，並透過傳送給 SNS 主題訂閱者的簡訊通知客戶失敗。

![\[Step Functions 使用 saga 模式實作的失敗保留範例。\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/patterns/images/pattern-img/fec0789c-d9b1-4d80-b179-dd9a7ecbec07/images/7c64d326-be27-42c3-b03f-d677efedb9a7.png)
