簡體   English   中英

在需要立即向用戶反饋的情況下應用 SAGA 模式

[英]Applying SAGA pattern in situations where immediate feedback to user is required

想象有一個應用程序,用戶有一個錢包,他們可以用現金充值,兌現或從外部系統購買,當用戶創建新的采購訂單時,我們首先從用戶的錢包中扣除金額。 然后向外部 API 發送一個 API 調用,說用戶購買了這些商品,我們從商家那里得到了關於購買是否成功的回復。 如果購買不成功,我們會將金額退還到用戶在我們系統上的錢包中。 然而,這里的一個關鍵注意事項是商家購買 API 端點可能會返回域錯誤的錯誤響應(用戶未注冊,用戶已停用,購買少於最低允許金額或高於最大金額)並且用戶立即獲得交易是否成功的確認響應,如果不成功,我們向用戶顯示我們從外部 API 獲得的失敗原因

我想將 saga 應用到上面的流程中,但是有一些挑戰

  1. 假設我們將使用消息代理(Kafka、rabbitmq)進行異步 saga 流,我們如何向用戶返回有關事務是否成功的響應? 異步事務可能因任何原因而失敗,如果失敗,可能需要一段時間來處理重試甚至在后台回滾。

  2. 即使我們能夠讓我們說使用類似 webhooks 的東西將結果通知前端/用戶,我們將數據推送到客戶端。 超時或技術故障會發生什么? 由於流程是異步的,因此可能需要一秒鍾或一個小時才能完成。 同時,用戶應該看到什么? 如果我們顯示超時錯誤,用戶可以重試該請求並以待處理的 state 中的 2 個請求結束,這些請求將在稍后處理,但用戶的意圖只是發出一個。

我無法向用戶顯示“已創建購買”之類的成功消息,然后稍后再通知他們,原因有二:

  1. 外部 API 經常返回域錯誤。 他們的反應是立即的。 因此,用戶看到此響應消息然后立即收到有關失敗的通知是沒有意義的
  2. 用戶必須能夠看到外部返回的錯誤信息 API

我們如何解決這個問題? 嘗試用 saga 解決它的主要原因是確保一致性並在失敗時重試,但考慮到這一點,我們如何處理用戶交互?

這就是我通過temporal.io開源項目解決這個問題的方法:

  1. 當用戶創建采購訂單時同步(等待完成)執行工作流。
  2. 工作流程從用戶錢包中扣除購買金額
  3. 工作流調用外部 API
  4. 如果 API 呼叫成功完成,則完成工作流。 這會取消阻止來自 (1) 的同步調用並向用戶顯示狀態。
  5. 如果 API 調用失敗(不等待結果)啟動另一個實現回滾的工作流。
  6. 使原始工作流程失敗。 這會將失敗返回給 (1) 處的調用者。 這允許向用戶顯示錯誤。
  7. 回滾工作流會根據需要執行回滾邏輯。

下面是使用 Java SDK 實現上述邏輯。其他支持的 SDK 有 Go、Typescript/Javascript、Python、PHP。

public class PurchaseWorkflowImpl implements PurchaseWorkflow {

  private final ActivityOptions options =
      ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build();
  private final Activities activities = Workflow.newActivityStub(Activities.class, options);

  @Override
  public void purchase(String accountId, Money amount, List<Item> items) {
    WalletUpdate walletUpdate = activities.deductFromWallet(accountId, amount);
    try {
      activities.notifyItemsPurchased(accountId, items);
    } catch (ActivityFailure e) {
      // Create stub used to start a child workflow.
      // ABANDON tells child to keep running after the parent completion.
      RollbackWalletUpdate rollback =
          Workflow.newChildWorkflowStub(
              RollbackWalletUpdate.class,
              ChildWorkflowOptions.newBuilder()
                  .setParentClosePolicy(ParentClosePolicy.PARENT_CLOSE_POLICY_ABANDON)
                  .build());
      // Start rollbackWalletUpdate child workflow without blocking.
      Async.procedure(rollback::rollbackWalletUpdate, walletUpdate);
      // Wait for the child to start.
      Workflow.getWorkflowExecution(rollback).get();
      // Fail workflow.
      throw e;
    }
  }
}

同步執行工作流的代碼

    PurchaseWorkflow purchaseWorkflow =
        workflowClient.newWorkflowStub(PurchaseWorkflow.class, options);
    // Blocks until workflow completion.
    purchaseWorkflow.purchase(accountId, items);

請注意,Temporal 確保工作流的代碼在出現各種類型的故障(包括進程崩潰)時保持運行,就好像什么也沒發生一樣。 所以所有的容錯方面都是自動處理的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM