概要
Koa ライクな API を提供する Node.js 製の gRPC フレームワーク、mali.js を試してみる機会があったので、簡単に使い方を紹介します。(公式サイトには Koa ライクとありますが、Express ライクと受け取って良いでしょう)
Node.jsのgrpc実装 grpc
パッケージのAPIをラップして簡単にしている印象があるので、手元でgRPCのmockサーバーが必要な際などに役立ちそうだと感じました。
※ 使ってみたという内容記事なので、実装上の tips や実運用でのノウハウなどに関しては触れません。
- 公式 Document
- GitHub
サーバー実装
gRPC のサーバー/クライアントを実装する際には、まずはじめに .proto
でサービス/メッセージタイプの定義を行う必要があります。また定義された.proto ファイルをローディングする方法には以下の二種類に分けられます。
- Dynamic codegen
- 実行時に
.proto
ファイルを解析してコード生成を行う
- 実行時に
- Static codegen
- 事前に
.proto
ファイルを解析してコード生成を行う(protoc
などのコンパイラを使用する)
- 事前に
また、今回は以下のような .proto
定義を使って紹介を進めます。
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHi (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
こちらは、Dynamic codegen を使って mali.js で gRPC サーバーを実装した例です。
import { resolve } from "path";
import Mali from "mali";
const PROTO_PATH = resolve(__dirname, "./protos/helloworld.proto");
function sayHello(ctx: any): void {
ctx.res = { message: `Hello ${ctx.req.name}` };
}
function sayHi(ctx: any): void {
ctx.res = { message: `Hi ${ctx.req.name}` };
}
export function main(): void {
const app = new Mali(PROTO_PATH, "Greeter");
app.use({ sayHello, sayHi });
app.start("localhost:50051");
}
main();
こちらは、Static codegen の例です。
まず、前提として以下の手順で、 ./static
ディレクトリに service class のコード生成を行っておきます。
yarn add -D grpc-tools grpc_tools_node_protoc_ts
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./static \
--grpc_out=./static \
--plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` \
protos/helloworld.proto
grpc_tools_node_protoc \
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
--ts_out=./static \
protos/helloworld.proto
上記の手順で生成された、service 定義を読み込み全体のコードは以下のようになります。
import Mali from "mali";
import * as services from "./static/protos/helloworld_grpc_pb";
import * as messages from "./static/protos/helloworld_pb";
function sayHello(ctx: any): void {
const reply = new messages.HelloReply();
reply.setMessage(`Hello ${ctx.req.name}`);
ctx.res = reply;
}
function sayHi(ctx: any): void {
const reply = new messages.HelloReply();
reply.setMessage(`Hi ${ctx.req.name}`);
ctx.res = reply;
}
export function main(): void {
const app = new Mali(services, "GreeterService");
app.use({ sayHello, sayHi });
app.start("localhost:50051");
}
main();
例えば、Node.js の grpc 標準実装である、 grpc
を使って同様のサーバーを実装しようとした場合、最低限のコードは以下のようになります。こちらは、Dynamic codegen の例です。
import { resolve } from "path";
import grpc from "grpc";
import protoLoader from "@grpc/proto-loader";
const PROTO_PATH = resolve(__dirname, "./protos/helloworld.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
function sayHello(call: any, callback: any): void {
callback(null, { message: `Hello ${call.request.name}` });
}
function sayHi(call: any, callback: any): void {
callback(null, { message: `Hi ${call.request.name}` });
}
function main(): void {
const server = new grpc.Server();
server.addService(hello_proto.Greeter.service, {
sayHello: sayHello,
sayHi: sayHi
});
server.bind("0.0.0.0:50051", grpc.ServerCredentials.createInsecure());
server.start();
}
main();
mali.js の Dynamic codegen の例と比較すると多少冗長なのが見て取れるかと思います。
mali.js では、Mali
のコンストラクタで .proto
ファイルのパスを渡すだけで gRPC サーバーの基本的な設定を行うことができます。
また、server に対して service の method を追加する方法も、koa で middleware を登録するように app.use()
の API を使って行うのも特徴的です。