iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Software Development

Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程系列 第 22

Day22 - Temporal Workflow Versioning 流程版本管理

  • 分享至 

  • xImage
  •  

1. 為什麼需要版本管理?(Determinism 與 Replay)

如之前的篇章提到過,Workflow 程式碼必須是確定性的(Deterministic):給定同一份 Event History,重播必須產生相同的決策與結果。

也就是說當發佈新版本的 Workflow 程式碼時,如果邏輯或指令序列和舊版本不同,舊流程在重播 (Replay) 歷史時就可能發生「非決定性錯誤(NonDeterministicWorkflowError)」造成中斷。

為了安全地升級流程,Temporal 提供了「Workflow Versioning」機制:

  • 以 API Workflow.getVersion 顯式標記變更點
  • 讓程式能對新舊版本作出一致且可重播的決策

以下列出需要版本化保護的變更清單:

2. 變更清單

類別 變更內容 是否需要 Workflow Versioning 說明
結構變更 新增/移除/改名 Activity 或 Child Workflow(包含更動 ActivityType 字串) ✅ 需要 會改變排程指令(ScheduleCommand)結構,Replay 時不一致
執行參數 改變重試、超時、上限等 Activity/Workflow Options(如 RetryPolicy、Timeout、Heartbeat、TaskQueue) ✅ 需要 這些設定會被寫入 Event History,影響 Replay 決策
控制流程 調整分支邏輯(if/else/switch)、迴圈次數、回呼註冊點、Signal 消費時機 ✅ 需要 分支或判斷改變會導致事件序列不同
事件時序 更動 Timer/Signal/Query 的註冊順序、等待條件(例如 Workflow.await(...) 的條件) ✅ 需要 會改變事件觸發順序或等待行為,Replay 產生不同事件流
非決定性來源 在 Workflow 中直接使用時間/亂數/UUID/外部 API(未透過 Activity 或可控 Clock) ✅ 需要 非決定性行為導致 Replay 不一致
內部實作 純 Activity 內部實作修改(不改變 Workflow 端呼叫方式與選項) ❌ 不需要 Workflow 只關心指令序列,Activity 實作不影響 Replay
重構優化 不改變指令序列與分支結果的重構(重命名、抽方法、結構調整、log 變更) ❌ 不需要 Replay 結果相同,屬安全重構

3. Workflow.getVersion 的設計思維 (change id 與 version)

  • Change Id 命名:以功能面命名 change id(如 "payment-method""ship-policy"),同一位置沿用相同 id。
  • 第一次上線:以 Workflow.getVersion(changeId, DEFAULT_VERSION, maxVersion) 標記;舊歷史走 DEFAULT_VERSION,新流程走 maxVersion
  • 後續演進:僅提高最大版本號(1 → 2 → 3),保留既有分支直到舊歷史結束。
  • 呼叫規則getVersion 會被記錄並重播,呼叫次數與順序必須固定。
  • 更名規則:change id 上線後不可改名;需演進就調高最大版本號,不更換 id。

4. 程式碼範例

import io.temporal.workflow.Workflow;

public class OrderWorkflowImpl implements OrderWorkflow {
  @Override
  public void run() {
    // 以 changeId "payment-method" 標記此變更點,將最大版本設為 1
    // 舊歷史重播會得到 DEFAULT_VERSION;新流程會得到 1
    int v = Workflow.getVersion("payment-method", Workflow.DEFAULT_VERSION, 1);
    if (v == Workflow.DEFAULT_VERSION) {
      // 舊版本路徑:保持原指令序列,確保重播一致
      chargeLegacy();
    } else {
      // 新版本路徑:套用新行為
      chargeV1();
    }
  }
}

多次演進:

// 同一個 changeId,將最大版本提升到 2(v0→v1→v2)
// 保留所有已上線過的分支,直到舊歷史完全結束
int v = Workflow.getVersion("payment-method", Workflow.DEFAULT_VERSION, 2);
if (v == Workflow.DEFAULT_VERSION) {
  chargeLegacy(); // v0:舊歷史重播
} else if (v == 1) {
  chargeV1();     // v1:第一版新行為
} else if (v == 2) {
  chargeV2();     // v2:第二版新行為
}

5. 安全升級流程

5.1 單次升級(old → new)

  • 在受影響的程式碼位置加入 Workflow.getVersion(changeId, DEFAULT_VERSION, newMax)
  • 舊路徑保留原指令序列;新路徑實作新行為,兩者皆可重播。
  • 撰寫單元與重播測試;配合可控的放量與回復策略,先以小量流量驗證再擴大。
  • 觀察新流程行為與指標;舊流程因重播得以安全續跑。

5.2 多次演進(v0 → v1 → v2 …)

  • 延用同一個 change id,每次僅提高「最大版本號」(1 → 2 → 3)。
  • 分支保留所有已上線過的版本實作,直到舊歷史全部結束。
  • 對長尾流程規劃 continueAsNew,在安全點加速切換至新版本。

5.3 降級與回復

  • 因保留舊分支,可回復(rollback)到舊版部署而不影響進行中的流程。
  • 未確認安全前,勿移除舊分支,避免舊歷史重播失敗。

5.4 何時可以安全移除舊分支?

滿足以下條件時,才能刪除舊版本邏輯:

  • 外部/業務:以業務指標(如老訂單已結案)與版本觀測(Workflow 回報實際使用版本)佐證。
  • 系統/技術:觀察期內無仍存活且可能走舊路徑的流程;或已在安全點以 continueAsNew 全數切到新程式碼。

結語

Temporal 核心是可重播的 Workflow。用版本化標記變更點,確保舊流程可重播、新流程走新行為;並配合可控放量與重播測試,達成不停機升級。


上一篇
Day21 - Workflow Cancel:設計能被中途喊停的流程
下一篇
Day23 - Temporal Testing (一):Workflow.sleep(), Activity.getExecutionContext()
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言