如果你是跟著文章一天接著一天實作的讀者,那麼你需要確保你的
Anser-Tutorial-Service
使用的是最新的v1.0.3
以上的版本;或者是在你本地的 Anser-Tutorial-Service 下執行composer update
將Anser
版本更新至v0.2.1
以上版本。
來到了 Anser-Orchestration 單元的最後一個章節了,在這個章節中我們將綜合前面章節的所有知識,進行一次完整的整合。本章與前面的章節會比較不一樣,我會詳細地闡述需求,你可以試著自己開發你的協作器。當然,本章一樣會給出一個筆者自己設計的協作器給予你參考。
在 Anser 的整個示範教學中,我們橫跨了三個微服務的溝通,分別是 User Service 、 Product Service 與 Order Service 。若我們得進行建立訂單的工作,那麼是必要與多個微服務進行溝通。建立一個新的訂單意味著訂單的庫存必須減少、訂單資訊必須被記錄,以及使用者餘額必須得到扣款。
你可以參考下圖大致了解整個微服務溝通的目標效果:
在這個需求中,我們會與微服務產生一次以上溝通,並且下一個步驟的行動通常會依賴於上一個步驟的行動的結果。分別是:
對於最外部接受使用者請求的 App 而言,會是一個這樣子的 create_order.php
PHP 應用程式 :
<?php
require_once './init.php';
use Services\Models\OrderProductDetail;
use Orchestrators\CreateOrderOrchestrator;
$productList = array_map(function($product){
return new OrderProductDetail(
p_key: $product['p_key'],
price: $product['price'],
amount: $product['amount']
);
}, json_decode(file_get_contents('php://input'), true));
$apiKey = $_SERVER['HTTP_AUTHORIZATION'] ?? "";
$result = (new CreateOrderOrchestrator($apiKey, $productList))->build();
header("Content-Type: application/json");
echo json_encode($result);
在上述的程式碼中,我們會將 Input 的 Json Body 轉換成內部的 OrderProductDetail
資料類別,你可以到 {project_root}/Orchestrators/CreateOrderOrchestrator.php
中看看這個類別的實作。總之,這個類別將是商品資料傳遞時的一個主要介面。
綜上所述,你應該可以透過 Postman 呼叫這個端點使協作器開始工作:
{{main_service}}/create_order.php
json Raw Body:[ { "p_key": 1, "price": 450, "amount": 5 }, { "p_key": 2, "price": 70, "amount": 4 }, { "p_key": 55, "price": 70, "amount": 400000 } ]
建立訂單的需求已經闡述完畢,你可以自己先撰寫看看,別忘了我們的協作器類別名稱應該要是 CreateOrderOrchestrator
並建立在: {project_root}/Orchestrators/CreateOrderOrchestrator.php
。
<?php
namespace Orchestrators;
use SDPMlab\Anser\Orchestration\Orchestrator;
use Services\OrderService;
use Services\ProductionService;
use Services\UserService;
use Services\Models\OrderProductDetail;
class CreateOrderOrchestrator extends Orchestrator
{
public UserService $userService;
public OrderService $orderService;
public ProductionService $productionService;
protected string $authToken;
/**
* @var OrderProductDetail[]
*/
public array $orderProducts;
public ?string $orderId = null;
/**
* CreateOrderOrchestrator
*
* @param string $authToken
* @param OrderProductDetail[] $orderProducts
*/
public function __construct(string $authToken, array $orderProducts)
{
$this->authToken = $authToken;
$this->orderProducts = $orderProducts;
$this->userService = new UserService();
$this->orderService = new OrderService();
$this->productionService = new ProductionService();
$this->orderId = $this->generateOrderId();
}
/**
* definition of orchestrator
*
* @return void
*/
protected function definition()
{
//Step0 取得使用者資訊(驗證使用者)
$this->setStep()->addAction(
alias: 'userInfo',
action: $this->userService->userInfoAction($this->authToken)
);
//Step1 取得產品最新資訊(用於取得最新售價)
$step1 = $this->setStep();
foreach ($this->orderProducts as $index => $orderProduct) {
$step1->addAction(
alias: 'product_' . ($orderProduct->p_key),
action: $this->productionService->productInfoAction($orderProduct->p_key)
);
}
//Step2 扣商品庫存
$step2= $this->setStep();
foreach ($this->orderProducts as $index => $orderProduct) {
$step2->addAction(
alias: 'product_' . ($orderProduct->p_key) . '_reduceInventory',
action: $this->productionService->reduceInventory($orderProduct->p_key, $this->orderId, $orderProduct->amount)
);
}
//Step3 建立訂單(將會取得訂單總價 total)
$this->setStep()->addAction(
alias: 'createOrder',
action: static function (CreateOrderOrchestrator $runtimeOrch) {
$userKey = $runtimeOrch->getStepAction('userInfo')->getMeaningData()['data']['u_key'];
//將最新商品售價更新至訂單資訊
foreach ($runtimeOrch->orderProducts as &$product ) {
$product->price = (int)$runtimeOrch->getStepAction('product_' . $product->p_key)->getMeaningData()['data']['price'];
}
return $runtimeOrch->orderService->createOrderAction($userKey, $runtimeOrch->orderId, $runtimeOrch->orderProducts);
}
);
//Step4 使用者錢包扣款
$this->setStep()->addAction(
alias: 'walletCharge',
action: static function (CreateOrderOrchestrator $runtimeOrch) {
$userKey = $runtimeOrch->getStepAction('userInfo')->getMeaningData()['data']['u_key'];
$total = $runtimeOrch->getStepAction('createOrder')->getMeaningData()['total'];
return $runtimeOrch->userService->walletChargeAction($userKey, $runtimeOrch->orderId, $total);
}
);
}
protected function defineResult(): array
{
$orderInfo = $this->getStepAction('createOrder')->getMeaningData();
$data = [
"success" => true,
"message" => "訂單建立成功",
"data" => [
"order_id" => $this->orderId,
"total" => $orderInfo['total'],
]
];
return $data;
}
protected function defineFailResult(): array
{
$failActions = $this->getFailActions();
$failMessages = [];
foreach ($failActions as $failAction) {
$failMessages[] = $failAction->getMeaningData();
}
$data = [
"success" => false,
"message" => "訂單建立失敗",
"data" => [
"order_id" => $this->orderId,
"fail_messages" => $failMessages,
]
];
return $data;
}
protected function generateOrderId(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0x0fff) | 0x4000,
random_int(0, 0x3fff) | 0x8000,
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff)
);
}
}
在上述的範例中有一些設計上的特點:
$runtimeOrch
進行取用。defineResult
和 defineFailResult
方法,提供了一個統一和明確的方式來處理正常和異常情況,無論協作器成功與否,都能夠獲得具有意義的回傳。generateOrderId()
方法利用 PHP 的 random_int 函數和 sprintf 函數生成一個符合 UUID 格式的唯一訂單ID。UUID 的生成確保每次創建訂單時,都產生一個唯一的 ID。在 Anser-Orchestration 單元的最後一章中,我們整合了前面章節的所有知識,完整展示如何建立一個訂單並與三個微服務溝通的協作器。透過這個實作案例,我們理解了在微服務架構中,如何透過協作器指揮不同服務間的互動,以完成一個複雜的業務流程。
明天,我們將進入分散式交易的單元。