ProtoBuf on Laravel

發表時間: 2021-11-19 15:23:20
一劃 @ 162.158.166.241

ProtoBuf on Laravel

實作 proto 建置, gRPC Server 及 gRPC Client。 以下內容是假定在 Laravel 已運行良好情況下開始設置。

實作環境

  • OS: Ubunto 20.04
  • PHP: 8.04
  • Laravel: 8

    運行環境需要的指令及延伸套件

  • Install PHP extension for protobuf
    $ sudo pecl install protobuf
  • Install protoc command
    $ sudo apt  install protobuf-compiler
  • Instal protobuf complier grpc
    $ sudo apt install protobuf-compiler-grpc

    Laravel

    可以新建一個 Laravel 專案來測試,或是在原本已有的 Larvel 專案。

  • Install google/protobuf to Laravel
    $ cd {laravel-project-folder}
    $ composer require google/protobuf
  • create protobuf folder structure to base path
    $ cd {laravel-project}
    $ mkdir protobuf
    $ mkdir protobuf/build
    $ mkdir protobuf/src
  • make *.proto file to protobuf/src

    example: user.proto

    syntax = "proto3";
    
    package mypackage;
    
    message UserRequest {
        uint32 id=1;
    }
    
    message User {
        uint32 id = 1;
        string name = 2;
        string email = 3;
        string created_at = 4;
        string updated_at = 5;
    }
    
  • generate protobuf program for php
    $ protoc --proto_path=protobuf  --php_out=protobuf/build protobuf/src/user.proto

    這個指令會自動幫你生成 protobuf 需要的 php 檔在protobuf/build 裡,這些檔,我們都不用去動它。

  • set psr-4 autoload for php in composer.json
        "autoload": {
            "psr-4": {
                "App\": "app/",
                "Database\Factories\": "database/factories/",
                "Database\Seeders\": "database/seeders/",
                "Mypackage\": "protobuf/build/Mypackage/",
                "": "protobuf/build/"
            }
        },

    到這裡,你可以試著用 protoc 產生的物件來試著轉換自己的 Model 資料。

gRPC on Laravel

Install grpc related packages

$ composer require grpc/grpc
$ composer require spiral/roadrunner-grpc

Download rr-grpc and protoc-gen-php-grpc command

Go to https://github.com/spiral/php-grpc/releases to downd latest version

  1. save rr-grpc to laravel base folder
    $ cd {laravel-project-folder}
    $ cp {download-folder}/{extrac-folder}/rr-grpc ./
  2. save protoc-gen-php-grpc to /usr/bin
    $ sudo cp {download-folder}/{extrac-folder}/protoc-gen-php-grpc /usr/bin

Generator protobuf files

參考上面Laravel的步驟,但這裡要加入 Server 的設定

Example

user.proto

syntax = "proto3";

package mypackage;

service UserService {
    rpc getUser(UserRequest) returns(User) {}
}

message UserRequest {
    uint32 id=1;
}

enum Gender {
    male = 0;
    female = 1;
    other = 2;
}

message User {
    uint32 id = 1;
    string name = 2;
    string email = 3;
    string created_at = 4;
    string updated_at = 5;
    message UserProfile {
        string nickname = 1;
        string intro = 3;
        Gender gender = 4;
        string birthday = 5;
    }
}

這個範例中加入了 rpc server 相關的設置

service UserService {
    rpc getUser(UserRequest) returns(User) {}
}

這個在我們產生相關檔案時,會自動幫我們建立 UserService 的 Interfeace, 裡面則有一個 getUser 的 Method 會傳回 User 這個 message 物件。

Generate protoBuf php files

$ protoc --proto_path=protobuf --php_out=protobuf/build  --grpc_out=protobuf/build --plugin=protoc-gen-grpc=/usr/bin/protoc-gen-php-grpc  protobuf/src/user.proto
Usage: protoc [OPTION] PROTO_FILES
OPTIONS
--proto_path: proto 檔案的所在資料夾
--php_out: php 檔產生處
--grpc_out: 用 protoc-gen-php-grpc 產生的檔案存放位置
--plugin: 使用 plugin, 這裡指定用 protoc-gen-grpc 這個 plugin, 後面再指定指令所在位置

實作 UserService

這裡就依自己在 Laravel 中習慣去建立你自己的物作。

建立 App/Serivces/UserService (app/Services/UserService.php)

<?php
namespace AppServices;

use AppModelsUser as DBUser;
use MypackageUser;
use MypackageUserRequest;
use MypackageUserServiceInterface;
use SpiralGRPC;

class UserService implements UserServiceInterface
{
    public function getUser(GRPCContextInterface $ctx, UserRequest $in): User
    {
        $userId = $in->getId();
        $user = DBUser::find($userId);
        $userProto = new User();
        $userProto->setId($user->id);
        $userProto->setName($user->name);
        $userProto->setEmail($user->email);
        $userProto->setCreatedAt((string) $user->created_at);
        $userProto->setUpdatedAt((string) $user->updated_at);

        return $userProto;
    }
}

建立 gRPC 執行的 worker

Create worker.php in base path on laravel

<?php
use IlluminateContractsConsoleKernel;

require_once __DIR__ . '/vendor/autoload.php';

/** 載入 Laravel 核心 */
$app = require_once __DIR__.'/bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();

/** 加入 gRPC Server 物件 */
$server = $app->make(SpiralGRPCServer::class);
/** 註冊想要的服務 */
$server->registerService(MypackageUserServiceInterface::class, new AppServicesUserService());
/** 啟始 worker */
$worker = new  SpiralRoadRunnerWorker(new SpiralGoridgeStreamRelay(STDIN, STDOUT));
$server->serve($worker);

設定 gRPC

gRPC 是由 rr-grpc 來啟動,它是由 rr 來改寫的,所以一樣吃 .rr.ymal 的設定檔
rr 是 roadrunner 的縮寫,指令即其縮寫。
.rr.yaml

grpc:
  listen: "tcp://0.0.0.0:50051"
  proto: "protobuf/src/user.proto"
  workers:
    command: "php worker.php"
    pool:
      numWorkers: 4

啟動 gRPC Server

$ ./rr-grpc serve -v -d

gRPC Server 就起來了,接下來設置 gRPC Client 來測試連線。

建立 gRPC Client for PHP

一樣由 protoc 來自動產生 php code

$ protoc --php_out=protobuf/build --grpc_out=protobuf/build  --plugin=protoc-gen-grpc=/usr/bin/grpc_php_plugin  protobuf/src/user.proto
--plugin: 使用 plugin, 這裡指定用 protoc-gen-grpc 這個 plugin, 改用 grpc_php_plugin 指令。

跟前面產生 UserServiceInterface.php 類似,但這裡產生 UserServiceClient.php

開始取得資料

建以一個 user.php 的指令

$ cd {laravel-project-folder}
$ touch user.php
$ chmod 755 user.php

user.php 內容

#!/usr/bin/php
<?php

require_once __DIR__ . '/vendor/autoload.php';
use MypackageUserServiceClient;
use MypackageUserRequest;
use GrpcChannelCredentials;

$client = new UserServiceClient('0.0.0.0:50051', [
    'credentials' => ChannelCredentials::createInsecure(),
]);

if (!isset($argv[1])) {
    die("Please specify the user id
");
}
$userId = $argv[1];

$request = new UserRequest();
$request->setId($userId);

/** @var User $response */
list($response, $status) = $client->getUser($request)->wait();
if ($status->code !== GrpcSTATUS_OK) {
    echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL;
    exit(1);
}
print $response->serializeToJsonString()."
";

用指令去取得資料

$ ./user.php {userId}

exampe: command

$ ./user.php 22

result

{"id":22,"name":"酈樺美","email":"dexter87@example.org","createdAt":"2021-10-26 15:03:51","updatedAt":"2021-10-26 15:03:51"}
一劃 @ 162.158.167.150

留言板程式太舊,markdown 支援不齊,好讀版 Go to https://hackmd.io/@tML6ejGhR7q68VfQ4kLDQg/By-WdVz_Y

回應文章