前面提到的電商註冊只是個簡單流程,它並不需要交易保證。
在單體應用中,只要以 Transaction begin/commit 或 @Transactional
就能跨表保證「全部成功或全部失敗」。發生錯誤時資料庫會自動 rollback,開發體驗單純。
但在分散式架構中,跨服務、跨資料庫、跨邊界的操作無法用單一資料庫交易涵蓋。
你會面臨最終一致性、網路失敗、重試等挑戰,這時候就需要以「補償」思維設計交易流程:
每個步驟各自完成與提交,一旦後續出錯,透過對應的反向操作把整體狀態拉回來。
Saga 是一種將「大交易」拆解為多個「本地交易」的模式。每個本地交易成功後立即提交;
若後續步驟失敗,系統依序執行對應的補償交易(Compensation)來撤銷先前已完成的效果,達到最終一致性。
重點特性:
在需要清楚掌控流程、錯誤與補償策略一致的中大型系統,建議採 Orchestration;如同本系一開始談到 Temporal 提供這樣的編排能力。
適用:
不適用:
這是一個商品上架的流程:建立商品主檔(T1)、設定價格(T2)、發佈到前台(T3)。
以上步驟屬於不同服務、不同資料庫,無法依靠單一交易機制保證全有或全無。
做法:把大交易拆成多個 Transaction。若某一步失敗,就透過補償(compensation)反向操作先前「已完成」的步驟。
紅色節點代表補償,它不是「資料庫的 rollback」,而是「業務邏輯的反向動作」。
例如 T3 發佈到前台失敗,就執行 revert pricing 與 delete product。
在 Temporal 中,可以非常簡單的用 Workflow 來編排流程,並以 Saga 物件管理補償:
Saga
物件。addCompensation
登記對應的反向操作。saga.compensate()
依相反順序逐一執行補償。這裡僅列出 ProductOnboardingWorkflowImpl
作為代表:
package com.flowzati.process2;
import io.temporal.activity.ActivityOptions;
import io.temporal.common.RetryOptions;
import io.temporal.workflow.Saga;
import io.temporal.workflow.Workflow;
import java.time.Duration;
public class ProductOnboardingWorkflowImpl implements ProductOnboardingWorkflow {
private final ProductActivities acts;
public ProductOnboardingWorkflowImpl() {
// 設定重試,讓流程在條件下可以順利度過暫時性的錯誤
RetryOptions retry = RetryOptions.newBuilder()
.setMaximumAttempts(2)
.setInitialInterval(Duration.ofSeconds(5))
.setBackoffCoefficient(2.0)
.build();
ActivityOptions ao = ActivityOptions.newBuilder()
.setRetryOptions(retry)
.setScheduleToCloseTimeout(Duration.ofSeconds(60))
.build();
this.acts = Workflow.newActivityStub(ProductActivities.class, ao);
}
@Override
public ProductPublishResult run(ProductInput input) {
Saga.Options sagaOptions = new Saga.Options.Builder().setParallelCompensation(false).build();
Saga saga = new Saga(sagaOptions);
try {
// Step 1: 建商品檔
String productId = acts.createOrUpdateProduct(input);
// 立刻 addCompensation 登記對應的反向操作 刪除商品檔。
saga.addCompensation(() -> acts.deleteProduct(productId));
// Step 2: 設定價格與可銷售
acts.setupPricingAndAvailability(productId, input);
// 立刻 addCompensation 登記對應的反向操作 刪除價格與可銷售紀錄。
saga.addCompensation(() -> acts.revertPricingAndAvailability(productId));
// Step 3: 發佈至商店頁面
acts.publishToStorefront(productId, input);
// 立刻 addCompensation 登記對應的反向操作 取消發佈至前台。
saga.addCompensation(() -> acts.unpublishFromStorefront(productId));
return new ProductPublishResult(productId, true, "Product published via STP");
} catch (Exception e) {
// 如果發生問題,只要判斷呼叫 saga.compensate(),就會按照相反的順序,一一執行反向操作。
saga.compensate();
return new ProductPublishResult(false, "Failed to publish product. Compensation executed: " + e.getMessage());
}
}
}
上述程式展現了 Saga 的核心邏輯:
productId
、requestId
)避免重複生效。Saga Pattern 用 Temporal 統一編排流程來實施分散式交易,並在實作時聚焦步驟切分、補償、重試、冪等來達到最終一致性。
下一篇我們來討論如何透過 Temproal 完成一個互動式的流程。