

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

# 系列事件協同運作模式
<a name="saga-orchestration"></a>

## 意圖
<a name="saga-orchestration-intent"></a>

系列事件協同運作模式使用中央協調器 (*協調器*) 來協助維持橫跨多個服務的分散式交易中的資料完整性。在分散式交易中，交易完成前可以呼叫多個服務。當服務將資料儲存在不同的資料存放區時，在這些資料存放區之間維持資料一致性可能會具有挑戰性。

## 動機
<a name="saga-orchestration-motivation"></a>

*交易*是可能涉及多個步驟的單一工作單元，除非完整執行所有步驟，否則不執行任何步驟，藉此使得資料存放區保持其一致的狀態。*原子性、一致性、隔離性和耐久性 (ACID)* 等詞彙定義了交易的屬性。關聯式資料庫提供 ACID 交易來維持資料一致性。

為了維持交易的一致性，關聯式資料庫使用兩階段遞交 (2PC) 方法。這種方法是由*準備階段*和*遞交階段*組成。
+ 在準備階段，協調處理會要求交易的參與處理 (參與者) 承諾遞交或回復交易。
+ 在遞交階段，協調處理會要求參與者遞交該筆交易。如果參與者無法同意在準備階段遞交，則系統會回復交易。

在遵循 database-per-service 設計模式的分散式系統中，系統不允許兩階段遞交。這是因為每筆交易均分散在不同的資料庫中，而且沒有單一控制器可以協調與關聯式資料存放區中的兩階段遞交類似的處理。在這種情況下，其中一種解決方案是使用系列事件協同運作模式。

## 適用性
<a name="saga-orchestration-applicability"></a>

出現下列情況時，請使用系列事件協同運作模式：
+ 在橫跨多個資料存放區的分散式交易中，您的系統需要資料完整性和一致性。
+ 資料存儲不提供 2PC 來提供 ACID 交易，並且在應用程式邊界內實作 2PC 是一項複雜的任務。
+ 您有不提供 ACID 交易的 NoSQL 資料庫，並且需要在單個交易中更新多個資料表。

## 問題和考量
<a name="saga-orchestration-issues"></a>
+ **複雜性**：補償性交易和重試會增加應用程式程式碼的複雜性，這可能會導致維護額外負荷。
+ **最終一致性**：本地端交易的序列處理會導致最終一致性，此情況對於需要強式一致性的系統來說可能是個挑戰。您可以設定業務團隊對一致性模式的期望來解決此問題，或透過切換到提供強式一致性的資料存放區的方式來解決此問題。
+ **等冪性：**系列事件參與者必須是等冪的，以便在由意外當機和協調器故障引起的暫時性故障時允許重複執行。
+ **交易隔離**：系列事件缺乏交易隔離。交易的並行協同運作可能會導致過時資料。我們建議使用語義鎖定來處理此類情況。
+ **可觀測性**：可觀測性是指詳細的日誌記錄和追蹤，藉此疑難排解執行和協同運作中的問題。當系列事件參與者的數量增加時，這會變得很重要，導致偵錯的複雜性。
+ **延遲問題**：當系列事件是由數個步驟組成時，補償性交易可能會增加整體回應時間的延遲。在這種情況下避免同步呼叫。
+ **單點失敗**：協調器可能會因為協調整個交易而成為單一失敗點。在某些情況下，由於這個問題，系列事件的編排模式是首選。

## 實作
<a name="saga-orchestration-implementation"></a>

### 高層級架構
<a name="saga-orchestration-implementation-high-level-arch"></a>

在下面的架構圖中，系列事件協調器有三個參與者：訂單服務、庫存服務和支付服務。完成交易需要三個步驟：T1、T2 和 T3。系列事件協調器知道的步驟，並按照所需的順序執行它們。當步驟 T3 失敗 (付款失敗) ，協調器執行補償交易 C1 和 C2 將資料恢復到初始狀態。

![\[系列事件協調器高層級架構\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-1.png)


當交易分散在多個資料庫中時，您可以使用 [AWS Step Functions](https://aws.amazon.com/step-functions/) 來實作系列事件協同運作。

### 使用 AWS 服務實作
<a name="saga-orchestration-implementation-aws-services"></a>

範例解決方案會使用 Step Functions 中的標準工作流程來實作系列事件協同運作模式。

![\[使用 Step Functions 實作系列事件工作流程\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-2.png)


當客戶呼叫 API 時，會調用 Lambda 函數，而預先處理會在 Lambda 函數中進行。函數會啟動 Step Functions 工作流程，以開始處理分散式交易。如果不需要預先處理，您可以直接從 API Gateway [啟動 Step Functions 工作流程](https://serverlessland.com/patterns/apigw-sfn)，而無需使用 Lambda 函數。

使用 Step Functions 可減輕以往實作系列事件協同運作模式時會出現的單點故障問題。Step Functions 具有內建的容錯能力，並維護每個 AWS 區域中多個可用區域的服務容量，以保護應用程式免受個別機器或資料中心故障的影響。這有助於確保服務本身及其操作的應用程式工作流程的高可用性。

#### Step Functions 工作流程
<a name="saga-orchestration-implementation-sfn"></a>

Step Functions 狀態機器可讓您針對模式實作設定以決策為基礎的控制流程需求。Step Functions 工作流程會呼叫個別服務，以進行訂單放置、存貨更新及付款處理，以完成異動，並傳送事件通知以供進一步處理。Step Functions 工作流程充當協調器來協調交易。如果工作流程包含任何錯誤，協調器會執行補償性交易，以確保跨服務維護資料完整性。

下列圖表顯示在 Step Functions 工作流程內執行的步驟。`Place Order`、`Update Inventory` 和 `Make Payment` 步驟表示成功路徑。訂單已下訂單，更新庫存，並在 `Success` 狀態傳回呼叫者之前處理付款。

`Revert Payment`、`Revert Inventory` 和 `Remove Order` Lambda 函數會指出當工作流程中的任何步驟失敗時，協調器執行的補償性交易。如果工作流程在 `Update Inventory` 步驟中失敗，協調器會先呼叫 `Revert Inventory` 和 `Remove Order` 步驟，再將 `Fail` 狀態傳回呼叫者。這些補償交易可確保維護資料的完整性。庫存會退回其原始層次，並回復訂單。

![\[系列事件 Step Functions 工作流程\]](http://docs.aws.amazon.com/zh_tw/prescriptive-guidance/latest/cloud-design-patterns/images/saga-orchestration-3.png)


### 範本程式碼
<a name="saga-orchestration-implementation-sample-code"></a>

下列範例程式碼顯示如何使用 Step Functions 建立系列事件協調器。若要檢視完整的程式碼，請參閱 [GitHub 儲存庫](https://github.com/aws-samples/saga-orchestration-netcore-blog)以取得此範例。

#### 任務定義
<a name="saga-orchestration-task"></a>

```
var successState = new Succeed(this,"SuccessState");
var failState = new Fail(this, "Fail");

var placeOrderTask = new LambdaInvoke(this, "Place Order", new LambdaInvokeProps
{
    LambdaFunction = placeOrderLambda,
    Comment = "Place Order",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var updateInventoryTask = new LambdaInvoke(this,"Update Inventory", new LambdaInvokeProps
{
    LambdaFunction = updateInventoryLambda,
    Comment = "Update inventory",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var makePaymentTask = new LambdaInvoke(this,"Make Payment", new LambdaInvokeProps
{
    LambdaFunction = makePaymentLambda,
    Comment = "Make Payment",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
});

var removeOrderTask = new LambdaInvoke(this, "Remove Order", new LambdaInvokeProps
{
    LambdaFunction = removeOrderLambda,
    Comment = "Remove Order",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(failState);

var revertInventoryTask = new LambdaInvoke(this,"Revert Inventory", new LambdaInvokeProps
{
    LambdaFunction = revertInventoryLambda,
    Comment = "Revert inventory",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(removeOrderTask);

var revertPaymentTask = new LambdaInvoke(this,"Revert Payment", new LambdaInvokeProps
{
    LambdaFunction = revertPaymentLambda,
    Comment = "Revert Payment",
    RetryOnServiceExceptions = false,
    PayloadResponseOnly = true
}).Next(revertInventoryTask);

var waitState = new Wait(this, "Wait state", new WaitProps
{
    Time = WaitTime.Duration(Duration.Seconds(30))
}).Next(revertInventoryTask);
```

#### 步進函數和狀態機器定義
<a name="saga-step"></a>

```
var stepDefinition = placeOrderTask
                .Next(new Choice(this, "Is order placed")
                    .When(Condition.StringEquals("$.Status", "ORDER_PLACED"), updateInventoryTask
                        .Next(new Choice(this, "Is inventory updated")
                            .When(Condition.StringEquals("$.Status", "INVENTORY_UPDATED"),
                                makePaymentTask.Next(new Choice(this, "Is payment success")
                                    .When(Condition.StringEquals("$.Status", "PAYMENT_COMPLETED"), successState)
                                    .When(Condition.StringEquals("$.Status", "ERROR"), revertPaymentTask)))
                            .When(Condition.StringEquals("$.Status", "ERROR"), waitState)))
                    .When(Condition.StringEquals("$.Status", "ERROR"), failState));

var stateMachine = new StateMachine(this, "DistributedTransactionOrchestrator", new StateMachineProps {
    StateMachineName = "DistributedTransactionOrchestrator",
    StateMachineType = StateMachineType.STANDARD,
    Role = iamStepFunctionRole,
    TracingEnabled = true,
    Definition = stepDefinition
});
```

### GitHub 儲存庫
<a name="saga-orchestration-implementation-github-repo"></a>

如需此模式範例架構的完整實作，請參閱 GitHub 存放庫，網址為 [https://github.com/aws-samples/saga-orchestration-netcore-blog](https://github.com/aws-samples/saga-orchestration-netcore-blog)。

## 部落格參考
<a name="saga-orchestration-blog"></a>
+ [使用系列事件協同運作模式建置無伺服器分散式應用程式](https://aws.amazon.com/blogs/compute/building-a-serverless-distributed-application-using-a-saga-orchestration-pattern/)

## 相關內容
<a name="saga-orchestration-resources"></a>
+ [系列事件編排模式](saga-choreography.md)
+ [交易寄件匣模式](transactional-outbox.md)

## 影片
<a name="saga-orchestration-videos"></a>

下列影片討論如何使用 實作 saga 協同運作模式 AWS Step Functions。


