Edited at

Node.js gRPC フレームワーク mali.js の紹介


概要

Koa ライクな API を提供する Node.js 製の gRPC フレームワーク、mali.js を試してみる機会があったので、簡単に使い方を紹介します。(公式サイトには Koa ライクとありますが、Express ライクと受け取って良いでしょう)

Node.jsのgrpc実装 grpc パッケージのAPIをラップして簡単にしている印象があるので、手元でgRPCのmockサーバーが必要な際などに役立ちそうだと感じました。

※ 使ってみたという内容記事なので、実装上の tips や実運用でのノウハウなどに関しては触れません。


サーバー実装

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 を使って行うのも特徴的です。


参考