iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Modern Web

30 天上手! PHP 微服務入門與開發系列 第 13

第十三章、Anser-Orchestration:處理服務協作的回傳 - PHP 微服務入門與開發

  • 分享至 

  • xImage
  •  

緊接著上一個章節,本章將討論如何處理一個協作器的回傳。

定義協作器執行結束後的回傳內容

協作器成功

延續上一章的範例程式碼,我們繼續往下看。

protected function defineResult(): array
{        
    $loginAction = $this->getStepAction('login');
    $infoAction = $this->getStepAction('info');
    $walletAction = $this->getStepAction('wallet');

    $data = [
        "success" => true,
        "message" => "協作器執行成功"
    ];
    $data['token'] =  $loginAction->getMeaningData()['token'];
    $data['userData'] = $infoAction->getMeaningData()['data'];
    $data['walletInfo'] = $walletAction->getMeaningData()['data'];
    return $data;
}

當所有步驟完成後,協作器需要回傳一個結果。可以透過覆寫 defineResult() 方法達成。在此方法中,你可以整合所有步驟的結果並提供一個格式化後的結果,以便你直接響應給客戶端或其他服務。若你沒有覆寫 defineResult(),那麼預設的響應將會是布林,其意義為所有參與協作的 Action 的 $action->isSuccess() 是否都為 true

UserLoginOrchestrator 中,我們從三個步驟中取得結果,並組合成一個包含token、用戶資料和錢包資訊的數據結構。

協作器失敗

除此之外,當協作器的某個步驟出錯或無法正常執行時,協作器應該能回傳一個描述錯誤的結果。你可以藉由宣告 defineFailResult() ,使回應的結構不會因為服務故障而無法產生預期的結果。某個微服務因為某些原因失效,你可以決定回傳一個部分資料的結果,並附加一個警告或錯誤訊息,而不是完全中斷整個流程。

舉個例子:


protected function defineFailResult(): array
{
    $loginAction = $this->getStepAction('login');
    $infoAction = $this->getStepAction('info');
    $walletAction = $this->getStepAction('wallet');

    $data = [
        "success" => false,
        "message" => "協作器執行失敗"
    ];

    $data['token'] = $loginAction->isSuccess() ? $loginAction->getMeaningData()['token'] : $loginAction->getMeaningData();
    if($loginAction->isSuccess() == false){
        $data['userData'] = "資料無法取得";
        $data['walletInfo'] = "資料無法取得";
        return $data;
    }

    if($infoAction != null){
        $data['userData'] = $infoAction->isSuccess() ? $infoAction->getMeaningData()['data'] : "部分資料無法取得";
    }

    if($walletAction != null){
        $data['walletInfo'] = $walletAction->isSuccess() ? $walletAction->getMeaningData()['data'] : "部分資料無法取得";
    }

    return $data;
}

在上述例子中,當協作器執行失敗時,我們將會回傳明確的錯誤訊息,即 defineFailResult() 宣告的內容。而只有部分資料可用時,也可以選擇只回傳可用的部分,並給予客戶端一個警告。這樣的處理方法讓前端接收到結果時,可以根據 successmessage 做出適當的反應。

這樣的設計不僅確保了服務的健壯性,同時也提供了更好的使用者體驗。畢竟,在微服務架構中,隨著服務的數量增加,某些服務偶爾出現問題是不可避免的。有效的錯誤處理和回傳策略能夠大大降低問題的衝擊。

最後,若你沒有覆寫上述方法,那麼協作器將會拋出 PHP 錯誤。

協作器中斷與失敗回傳

在上一個小節的程式碼中,你應該能察覺到一些有趣的地方:

$infoAction = $this->getStepAction('info');
if($infoAction != null){
    $data['userData'] = $infoAction->isSuccess() ? $infoAction->getMeaningData()['data'] : "部分資料無法取得";
}

為什麼我們需要使用 if($infoAction != null) 進行判斷?首先,getStepAction(string $alias) 可能會有兩種回傳結果:

  • Action 類別:在協作器中搜索到正確別名的某個 Action。
  • null:有三個可能的原因造成這個回傳。
    1. $alias 拼字錯誤
    2. setAction() 中傳入匿名函數所導致
    3. 使用 $this->setStep()->addDynamicActions() 加入動態 Action 所導致

addDynamicActions() 是之後的章節才會提到的內容,所以本章節先行略過。我們重點地關注第 2 點問題。首先我們先回顧前一章提到的協作器邏輯:

$this->setStep()
    ->addAction(
        'login',
        $this->userService->userLoginAction($email, $password)
    );
$this->setStep()
    ->addAction(
        'info',
        static function(UserLoginOrchestrator $runtimeOrch){
            $data = $runtimeOrch->getStepAction('login')->getMeaningData();
            return $runtimeOrch->userService->userInfoAction($data['token']);
        }
    );
$this->setStep()
    ->addAction(
        'wallet',
        static function(UserLoginOrchestrator $runtimeOrch){
            $data = $runtimeOrch->getStepAction('info')->getMeaningData();
            return $runtimeOrch->userService->walletAction($data['data']['u_key']);
        }
    );

除了第一個步驟是直接宣告了明確的 Action 外,第二與第三步驟皆是依賴前一個步驟執行結果的 Action Callback。試問,若編排器執行到了第一步驟發生了問題,那麼第二與第三步驟的匿名函數還會被執行到嗎?

答案是不會。如果在協作器的執行過程中,某一步驟的 Action 發生了錯誤,那麼協作器會立刻終止後續的 Action 並直接進行錯誤回傳的流程。因此,後續步驟的匿名函數不會被觸發。

這裡涉及到協作器的一個重要特性,那就是當任何一個步驟發生錯誤時,協作器會保護整個流程,確保它不會進入到不確定的狀態。這也是為何在 defineFailResult() 中,我們需要特別處理各步驟的回傳資料和狀態,以確保錯誤的資訊能夠清楚地傳達給客戶端。

讓我們繼續看例子。如果 login 步驟失敗了,那麼 getStepAction('info')getStepAction('wallet') 實際上是不會回傳任何 Action 的,因為這兩個步驟的匿名函數都還沒有被觸發。這就是為什麼我們需要在 defineFailResult() 裡面進行判斷,確保對未被觸發的步驟進行適當的處理。

再進一步的,當我們在撰寫協作器時,必須要了解這樣的錯誤處理機制,這樣才能確保當系統出現問題時,能夠優雅地回應錯誤給客戶端,而不是產生不可預測的結果。另外,也需要對各步驟間的依賴關係有所了解,確保協作器的各個步驟都能夠正常工作。

執行服務協作

你可以在任一進入點中 new 出你的服務協作實體,並透過 build($args) 執行你的協作器,以本章的範例來說,你可以在專案根目錄找到 userlogin.php 就像這樣:

<?php

require_once './init.php';

use Orchestrators\UserLoginOrchestrator;

$userOrch = new UserLoginOrchestrator();
$result   = $userOrch->build($_POST['email'], $_POST['password']);

header("Content-Type: application/json");
echo json_encode($result);

使用 Postman 進行正確的呼叫,應該能看到符合我們預期的結果:

讓我們來試試輸入錯誤的帳號密碼吧?

完美,一切都在我們的掌控之中!

結語

本章的範例為我們展示了如何使用協作器去建構一個簡單的使用者登入流程。從中,我們看到了如何組合多個步驟,以及如何有效地處理各種錯誤情況。值得注意的是,實際的應用場景可能會比這個範例更加複雜,因此開發者在應用協作器模式時,需要不斷地思考和調整,以確保最終的實現能夠滿足業務的需求。

服務協作器不僅提供了一個組織和管理複雜業務流程的框架,更重要的是,它給我們帶來了一種設計模式,讓系統能夠在微服務的世界中,優雅地處理各種狀況。透過明確的步驟定義和彈性的錯誤處理機制,開發者可以確保即使在面對不確定性和錯誤時,系統仍能提供清晰、一致的回應。

最後,希望你能夠從本章中獲得一些啟發,並將這些知識應用到你的實際開發中。


上一篇
第十二章、Anser-Orchestration:處理服務協作邏輯 - PHP 微服務入門與開發
下一篇
第十四章、Anser-Orchestration:「順序」與「並行」共存的複雜服務協作 - PHP 微服務入門與開發
系列文
30 天上手! PHP 微服務入門與開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言