從這個章節開始,我們將會使用到第四章中除了 Main App 以外的所有服務。因此,尚未建立相關環境的讀者請前往第四章並依照教學建立起你的本地環境;除此之外,我們將使用 Anser-Tutorial-Service
作為我們的示範環境。請至 GitHub Releases 中下載所需的檔案。
將上述檔案解壓縮至本地環境後,你應該能看到一個由 Filters
、Logs
、Orchestrators
與 Services
等資料夾組成的專案。這個專案結構即是 Anser 所推薦的組織模式,所有專案的檔案皆遵循 PSR-4 自動載入標準中關於檔案名稱與資料夾命名的規範。
使用 Command Line 介面定位至 Anser-Tutorial-Service
目錄下,以 docker compose up -d
啟動環境。這裡需要注意的是,這個開發環境與第四章的 Main App
使用同一個外部 port
8083,若你是一路依序實作過來的讀者,則必須先關閉本地的 Main App。
接著,使用 composer install
將專案所需的依賴檔案給下載回來,此時你的資料夾應該會長的像是這個樣子:
未來所有的程式進入點(Postman 存取的 PHP 檔案)都需要 require_once init.php
,若你仔細觀察已經存在的檔案,你應該能夠發現他們的 require_once
都被拿掉了。這是因為我們採用了 composer 的 psr-4 autoload
功能,你可以參考根目錄下的 composer.json
:
因為第六行到第九行的宣告,使我們的 composer 在執行 composer update
或 composer install
時會自動掃描符合 psr-4
規範的資料夾,再將它們處理成自動載入的腳本,這樣我們就能免除在檔案中加上 require_once
的不便了。若未來讀者需要自行建立資料夾或檔案,可以直接執行 composer dump-autoload
,這個指令將會重新掃描資料夾與其他規則,再重新產生 autoload
所需的資料。
最後,因為 Anser-Tutorial-Service
專案已經實作了所有第四章提到的所有 Service Endpoints,所以你的本地環境必須同時擁有 Production Service
、User Sercie
以及 OrderService
。
你應該在 {project_root}/Orchestrators
中發現上週提到的 UserLoginOrchestrator
,本周會詳細的介紹 Anser 的服務協作邏輯與協作器的使用方式。
<?php
namespace Orchestrators;
use SDPMlab\Anser\Orchestration\Orchestrator;
use Services\UserService;
class UserLoginOrchestrator extends Orchestrator
{
protected $userService;
public function __construct()
{
$this->userService = new UserService();
}
protected function definition(string $email = "", string $password = "")
{
$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']);
});
}
protected function defineResult(): array
{
$data = [
"token" => $this->getStepAction('login')->getMeaningData()['token'],
"userData" => $this->getStepAction('info')->getMeaningData()['data'],
"walletInfo" => $this->getStepAction('wallet')->getMeaningData()['data']
];
return $data;
}
}
在上述的範例協作器中,我們首先是在類別的建構子中初始化了 UserSercie
類別,這個類別在第九章的服務抽象化有清楚的解釋。透過抽象化的服務類別,就可以在協作器中以明確語意化的方式取得需要被存取的準確服務端點。
所有繼承 SDPMlab\Anser\Orchestration\Orchestrator
的協作器,都需要覆寫 definition()
方法來定義協作器的執行細節。值得注意的是,因為父類別的 definition()
方法為一個保護方法 protected function definition();
,因此在定義這個方法所需的傳入參數時,你必須明確地給予參數們初始值,就像範例那樣:
protected function definition(string $email = "", string $password = "")
接著,你可以透過 setStep()
方法,開始撰寫你的協作器編排邏輯。協作邏輯的實作基礎是建立一系列的步驟(Step)。每個步驟可以代表一個甚至是複數的微服務操作或請求,在 Anser 中我們稱這種溝通為行動(Action)。透過setStep()方法,你可以建立新的步驟並透過鏈式操作(Chain Operation)加入該步驟需要的行動(Action)。
例如,在 UserLoginOrchestrator
中,我們建立了三個步驟:
第一步驟:進行用戶登入,其行動名稱為 login
。
第二步驟:取得用戶資訊,其行動名稱為 info
。
第三步驟:取得用戶的錢包資訊,其行動名稱為 wallet
。
呼叫 setStep()
後,協作器將會自動宣告一個空的 Step
類別實體,並將它回傳給你。接著,每個步驟都可以透過 addAction()
方法添加操作。該方法接受兩個參數:
string $alias
本次行動的別名Closure|ActionInterface $action
與行動的內容$action
可以是一個已經實體化的 Action 類別(例如:UserService
直接回傳的 Action
),或是一個可呼叫的匿名函數。
若你選擇直接傳入 Action
實體至 addAction()
,那麼協作器將會自動幫你掌管這個 Action 的生命週期,在輪到它的時候對微服務展開連線。
若是你選擇傳入匿名函數,那麼你將可以獲得執行時期的協作器,你可以在這個函式中進行更多的邏輯操作或動態地根據前面步驟的結果來呼叫微服務。這提供了開發人員高度的彈性,允許協作器在執行時進行動態決策。比如說:你可能需要根據第一步驟的結果,來決定第三步驟具體要呼叫哪個微服務。
依據在上述的 info
步驟中,我們使用了一個匿名函數。這接著從 login
步驟中取得的資料來決定怎麼取得用戶的資訊。
->addAction('info', static function(UserLoginOrchestrator $runtimeOrch){
$data = $runtimeOrch->getStepAction('login')->getMeaningData();
return $runtimeOrch->userService->userInfoAction($data['token']);
});
透過在 addAction()
中傳入匿名函數,藉由取得執行時期當下的協作器 $runtimeOrch,就可以使用它來取得先前步驟的結果。這邊我們使用 getStepAction(string $alias)
來取得上一個步驟的的 login
Action。
你需要特別注意的是,在你完成了所有的邏輯判斷後,你必須在這個匿名函數中回傳一個 Action 實體。
透過上述的解說,我們深入地了解了如何使用 Anser 來指揮簡單的微服務協作。協作器是指揮微服務互動的關鍵部分。透過定義各個步驟和行動,你可以精確地控制每個微服務應該在何時被呼叫。
每個步驟可以包含一個或多個行動,這些行動可以是靜態的(直接呼叫一個服務)或動態的(基於先前步驟的結果來決定的行動);透過使用匿名函數,開發人員可以根據先前步驟的結果進行動態的邏輯決策。這提供了非常大的彈性,允許協作器根據執行時的實際情況進行調整。
微服務的編排並不是一件簡單的事,但有了 Anser 這樣的工具,開發者可以更有效地建立、測試和維護複雜的微服務互動。