實作基於 Webhook 的「翻譯管理系統」(一):服務端架構

前言

此專案目的是實做一個實驗性質的、可雙向溝通的翻譯管理系統,目的是讓使用者可以從後台直接修改翻譯文案,並線上更新專案的語系資源。

流程

為了讓客戶端的語系資源被更新,服務端不會直接將資料發送給客戶端。而是透過 Webhook 的機制,送出一個通知給客戶端,讓客戶端主動獲取資源。流程如下:

  1. 服務端向客戶端發送請求,希望客戶端執行某些「事件」。
  2. 客戶端接收服務端請求,根據這些「事件」所定義的行為,向服務端發起請求。
  3. 服務端接收客戶端請求,回傳語系資源。
  4. 客戶端接收語系資源,產生語系檔案。

使用 Webhook 的好處是,客戶端只是被通知要做什麼,仍然保有主動權向服務端獲取資源。

專案架構

分為 5 個部分:

  1. lexicon-server:服務端後端
  2. lexicon-client:服務端前端
  3. lexicon-api-laravel-client:客戶端 Laravel 套件
  4. lexicon-api-php-client:客戶端 PHP 套件
  5. lexicon-demo:客戶端
1
2
3
4
5
6
7
8
9
|----------------------|-----------|------------------------------------|
| lexicon-server | | lexicon-demo |
| |----------------| | | |------------------------------| |
| | lexicon-client | |-----------| | lexicon-api-laravel-client | |
| |----------------| | Webhook | | |------------------------| | |
| |-----------| | | lexicon-api-php-client | | |
| | | | |------------------------| | |
| | | |------------------------------| |
|----------------------|-----------|------------------------------------|

資料模型

主要模型有:

  1. User:使用者
  2. Team:團隊
  3. Project:專案
  4. Language:語言
  5. Form:語言型態
  6. Key:翻譯鍵
  7. Value:翻譯值
  8. Setting:設定
  9. Hook:客戶端 Webwook 網址

核心

發送事件

服務端的 DispatchController 控制器用於將事件發送給客戶端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
namespace App\Http\Controllers\Api\Project;

use App\Http\Controllers\Controller;
use App\Http\Requests\Project\ProjectDispatchRequest;
use App\Models\Hook;
use App\Models\Project;
use App\Services\ProjectService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\Response;

class DispatchController extends Controller
{
/**
* @var ProjectService
*/
private ProjectService $projectService;

/**
* Instantiate a new controller instance.
*
* @param ProjectService $projectService
*/
public function __construct(
ProjectService $projectService
) {
$this->projectService = $projectService;
}

/**
* Dispatch events to client.
*
* @param ProjectDispatchRequest $request
* @return JsonResponse
*/
public function __invoke(ProjectDispatchRequest $request)
{
/** @var Project $project */
$project = $request->user();

// 向專案所有的 webhooks 發出請求
$project->hooks->each(function (/** @var Hook $hook */ $hook) use ($request) {
Http::retry(3, 500)
->withHeaders([
'Authorization' => sprintf('Bearer %s', $request->bearerToken())
])
->post($hook->url, [
'events' => $request->input('events'),
])
->throw();
});

return response()->json(null, Response::HTTP_ACCEPTED);
}
}

提供資源

客戶端接收到通知後,會使用 Lexicon 客戶端套件向服務端獲取資源,而 ProjectController 控制器會提供專案的所有翻譯鍵和翻譯值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace App\Http\Controllers\Api\Project;

use App\Http\Controllers\Controller;
use App\Http\Requests\Project\ProjectShowRequest;
use App\Http\Resources\Project\ProjectResource as Resource;
use App\Services\ProjectService;

class ProjectController extends Controller
{
/**
* @var ProjectService
*/
private ProjectService $projectService;

/**
* Instantiate a new controller instance.
*
* @param ProjectService $projectService
*/
public function __construct(
ProjectService $projectService
) {
$this->projectService = $projectService;
}

/**
* Display a listing of the resource.
*
* @param ProjectShowRequest $request
* @return Resource
*/
public function show(ProjectShowRequest $request)
{
$project = $this->projectService->get(
$request->user(),
$request,
$request->input('cached', false)
);

return new Resource($project);
}
}

程式碼