Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

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

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away