iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0

前面的篇幅雖然啟動了三個流程,但剛接觸 Temporal 時,會很困惑「啟動方式很多,要選哪一個?」

本篇聚焦「啟動流程」,搭配對照表與極短範例,幫你迅速理解每種啟動方式的語意、是否同步、典型使用情境與常見陷阱,並提供簡單的決策指南協助選擇正確 API。

1. 流程啟動 Workflow Start 對照表

方法 類型 是否同步 功能 輸入參數 回傳型別 適用情境 常見陷阱 典型業務案例
WorkflowClient.execute(...) 靜態 可選(同步/非同步) 啟動,回傳 CompletableFuture;等待才會阻塞 Workflow stub method 參數 CompletableFuture<T> 有 typed stub,要一次拿結果 呼叫端被 block,長流程會卡住 一次性批次處理:產生報表並等待產出檔案
WorkflowClient.start(...) 靜態 非同步 啟動但不等結果 Workflow stub method 參數 WorkflowExecution 有 typed stub,只想非同步跑 忘記 getResult() → 沒人收結果 非同步寄信流程,結果可晚點查
Typed Stub method(...) 物件 同步 直接呼叫 interface 方法即啟動 Workflow 定義的方法參數 T 最常見用法,語意清楚 workflowId 管理不當 → 重複啟動 新用戶註冊 → 建立帳號 workflow
WorkflowStub.start(...) 物件 非同步 以 untyped stub 啟動 Workflow start 參數(無方法名) WorkflowExecution 只能透過動態呼叫,無法 import interface 型別不安全,錯參數 runtime error 跨模組啟動:BFF 層啟動 Workflow
signalWithStart(...) 物件 非同步 Workflow 不存在 → 新啟動並送 Signal;已存在 → 直接送 Signal signal 名稱, signal args, start args WorkflowExecution 不確定 Workflow 是否已存在 workflowId 設錯 → 啟動多個流程 客服審核流程:使用者送出申訴
executeUpdateWithStart(...) 物件 可選(同步/非同步) Workflow 不存在 → 啟動並送 Update;可選擇等結果 update 名稱, UpdateOptions, update args, start args TWorkflowUpdateHandle<T> 更新 Workflow 狀態但不確定是否存在 Event History 膨脹、要注意 retry/timeout 支付流程:若不存在 → 建立並更新付款狀態

2. 程式碼範例

首先連線 Server,並準備 Workflow 啟動選項:

// 建立與 Temporal 服務的用戶端(可重用、執行緒安全,建議應用程式生命週期內共用)
WorkflowClient client = WorkflowClient.newInstance(service);
// 建構 Workflow 啟動選項(Start 時使用)
WorkflowOptions options = WorkflowOptions.newBuilder()
    // 指定 Task Queue,必須與對應 Worker 的 taskQueue 一致,否則拿不到任務
    .setTaskQueue("user-task-queue")
    // 指定此 Workflow 的唯一識別(建議使用穩定/自然鍵:例如 registration-{email})
    // 可避免重複啟動;若相同 ID 已存在則預設會拒絕啟動
    .setWorkflowId("registration-123")
    // 完成建構,取得不可變的 WorkflowOptions
    .build();

2.1 WorkflowClient.execute(...) — 同步啟動並等待結果

RegistrationWorkflow workflow = client.newWorkflowStub(RegistrationWorkflow.class, options);
CompletableFuture<Object> cf = WorkflowClient.execute(workflow::register, "alice@example.com");
Object result = cf.get();

2.2 WorkflowClient.start(...) — 非同步啟動,稍後再取結果

RegistrationWorkflow workflow = client.newWorkflowStub(RegistrationWorkflow.class, options);
WorkflowExecution exec = WorkflowClient.start(workflow::register, "alice@example.com");

// 需要之後再取結果
String result = WorkflowStub.fromTyped(workflow).getResult(String.class);

2.3 Typed Stub method(...) — 直接呼叫介面,預設同步取得結果

RegistrationWorkflow workflow = client.newWorkflowStub(RegistrationWorkflow.class, options);
String result = workflow.register("alice@example.com");

2.4 WorkflowStub.start(...) — 以 Untyped Stub 動態啟動(無方法名),非同步啟動

RegistrationWorkflow typed = client.newWorkflowStub(RegistrationWorkflow.class, options);
WorkflowStub dynamic = WorkflowStub.fromTyped(typed);
WorkflowExecution exec = dynamic.start("alice@example.com");

2.4 補充. 直接建立 Untyped Stub(無需 Typed 介面)

// 若無法引用 Workflow 介面,可直接以 Workflow Type 建立 untyped stub
WorkflowStub untyped = client.newUntypedWorkflowStub("RegistrationWorkflow", options);
WorkflowExecution exec2 = untyped.start("alice@example.com");

註:Workflow Type 字串需與 Worker 所註冊的 Workflow type 一致。直接建立的 untyped stub 與 fromTyped(...) 取得的效果相同,差異在於前者不依賴介面,後者可沿用 typed stub 的 options 與識別設定。

跨程式/跨語言使用:用戶端只需能連到 Temporal Server,即可跨進程或微服務啟動目標 Workflow。此時常見做法是使用 newUntypedWorkflowStub("<WorkflowType>", options) 啟動,並以名稱字串呼叫 signal(...)/query(...)/executeUpdate(...);確保 workflowTypetaskQueueworkflowId 與資料序列化(DataConverter,例如 JSON/Protobuf)在不同程式間對齊即可。

2.5 signalWithStart(...) — 不在則啟動並送 Signal,在則直接送 Signal

RegistrationWorkflow typed = client.newWorkflowStub(RegistrationWorkflow.class, options);
WorkflowStub dynamic = WorkflowStub.fromTyped(typed);

WorkflowExecution exec = dynamic.signalWithStart(
    "signalMethodName",                       // signal 名稱
    new Object[] { "ticket-001", "內容" }, // signal 參數
    new Object[] { "alice@example.com" }  // start 參數
);

2.6 executeUpdateWithStart(...) — 不在則啟動並送 Update,可選擇是否等待結果

RegistrationWorkflow typed = client.newWorkflowStub(RegistrationWorkflow.class, options);
WorkflowStub dynamic = WorkflowStub.fromTyped(typed);

Object o = dynamic.executeUpdateWithStart(
    UpdateOptions.newBuilder().setUpdateName("update").build(),  // UpdateOptions
    new Object[] { "order-123", 1000 }, // update 參數
    new Object[] { "order-123" }        // start 參數(預設 Workflow 方法)
);

2.6 補充. UpdateOptions

  • updateId(冪等鍵):為同一筆業務更新提供唯一識別,避免呼叫重試或網路波動造成重複處理。
  • 等待階段(wait stage):可選擇只等到「已受理」或一路等到「已完成」。
    • 只需確認已送達(不阻塞取得最終結果)→ 等到「已受理」。
    • 需要同步拿結果(阻塞到完成)→ 等到「已完成」。
  • Metadata/Headers(選用):可附帶追蹤或稽核資訊,方便除錯與觀測。
  • 命名建議updateId 使用自然鍵,如 recordPayment-{orderId}-{attempt},兼顧可讀與冪等。

3. 啟動決策指南(Decision Guide)

先回答三個問題,就能快速選 API:

  1. 需不需要同步拿結果?
    • 需要 → WorkflowClient.execute(...)Typed Stub method(...)
    • 不需要 → 繼續看 2
  2. 只想非同步啟動,稍後再收結果?
    • 是 → WorkflowClient.start(...) + WorkflowStub.getResult(...)WorkflowStub.start(...)
    • 否 → 繼續看 3
  3. 不確定 Workflow 是否存在,需要 upsert 語意?
    • 是 → signalWithStart(...)executeUpdateWithStart(...)
    • 否 → 若無法引用介面,用 WorkflowStub.start(...);否則用 Typed Stub 直接呼叫

常見反模式:

  • 長流程用 execute(...) 導致呼叫端阻塞
  • 用 Signal 承載需要「被受理與回覆」的需求(應改用 Update)

4. workflowId 與冪等性

  • 建議使用自然鍵或穩定鍵:例如 order-{orderId}registration-{emailHash}
  • 重複呼叫時,以相同 workflowId 導向同一個 Workflow 避免重複啟動
  • signalWithStart(...)/executeUpdateWithStart(...) 可實作 upsert:存在則訊息或更新,不在則啟動

結語

本篇整理了 6 種 Workflow 啟動方式,並以「是否同步」輔助選擇,搭配精簡範例與決策指引,協助選對 API。

下一篇將進入 Message Passing:介紹 Signal、Query、Update,串起從「啟動 → 互動」的完整設計。


上一篇
Day 15 - Temporal 消除排程與狀態管理,流程寫得更直覺
下一篇
Day17 - Temporal 互動 API 的設計指南(Signal / Query / Update)
系列文
Temporal 開發指南:掌握 Workflow as Code 打造穩定可靠的分散式流程21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言