Deno 🦕 で gRPC 🦮 試してみた
はじめに
最近、少し Deno 使い始めた。
Deno で gRPC サーバー・クライアントどんな感じでできるんやろうと思って、検索の最初に引っかかったgrpc_basic@0.4.6 | Denoを使ってみることにした。Very basic gRPC implementation for Deno
らしい。
You probably should wait for more mature and standard aligned implementation
って書いてあるし、リポジトリ(prohazko2/deno-grpc)見てみると、今日(2022/10/30)現在、スターは 36 で心もとないけど、雰囲気しれたら良いかなと思って試してみることにした。
(一方的に知ってる)mattn さんが試してたのも one of やってみた理由 s
Issue にも上がってるから、公式でもそのうちサポートされそう。どんな感じになるのか、いまいちわかんないけども。
To give context, we believe that integrated gRPC to the Deno CLI is an important feature and are committing core team resources to delivering it.
Add support for gRPC · Issue #3326 · denoland/deno
コード
example そのままでも動いたけど、少し修正したり CLI クライアントで呼び出したりしてみた。
proto
まず、proto を定義
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc ShoutHello (HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
コード(interface)生成
下記コマンドでコード生成。
deno run --allow-read https://deno.land/x/grpc_basic@0.4.6/gen/dts.ts ./greeter.proto > ./greeter.d.ts
こんなファイルが生成される。シンプルなインターフェースが入ってる。
/* this code was generated by automated tool,
should not edit by hand */
export interface Greeter {
SayHello(request: HelloRequest): Promise<HelloReply>;
ShoutHello(request: HelloRequest): AsyncGenerator<HelloReply>;
}
export interface HelloRequest {
name?: string;
}
export interface HelloReply {
message?: string;
}
サーバー
こんな感じで動かす。自動生成したコードの中身を記載してaddService()
で登録する感じ。オブジェクトのメソッドっぽく書かれてたのをクラスにしてみた。
import { GrpcServer } from "https://deno.land/x/grpc_basic@0.4.6/server.ts";
import { Greeter, HelloReply, HelloRequest } from "./greeter.d.ts";
const port = 50051;
const server = new GrpcServer();
const protoPath = new URL("./greeter.proto", import.meta.url);
const protoFile = await Deno.readTextFile(protoPath);
class GreeterImp implements Greeter {
async SayHello(req: HelloRequest) {
const message = `hello ${req.name || "stranger"}`;
return { message: message };
}
async *ShoutHello(req: HelloRequest) {
for (const n of [0, 1, 2]) {
const message = `hello ${req.name || "stranger"} #${n}`;
yield { message: message };
}
}
}
const greeter = new GreeterImp();
server.addService<Greeter>(protoFile, greeter);
console.log(`gonna listen on ${port} port`);
for await (const conn of Deno.listen({ port })) {
server.handle(conn);
}
動かすときはこんな感じ。
deno run --allow-read=./greeter.proto --allow-net server.ts
クライアント
クライアントはそのまま。RPC らしく関数っぽく呼べる。素晴らしい。
import { getClient } from "https://deno.land/x/grpc_basic@0.4.6/client.ts";
import { Greeter } from "./greeter.d.ts";
const protoPath = new URL("./greeter.proto", import.meta.url);
const protoFile = await Deno.readTextFile(protoPath);
const client = getClient<Greeter>({
port: 50051,
root: protoFile,
serviceName: "Greeter",
});
/* unary calls */
console.log(await client.SayHello({ name: "unary #1" }));
console.log(await client.SayHello({ name: "unary #2" }));
/* server stream */
for await (const reply of client.ShoutHello({ name: "streamed" })) {
console.log(reply);
}
client.close();
コマンドはこんな感じ。
deno run --allow-read=./greeter.proto --allow-net client.ts
CLI クライアント
ついでに、Evans使って、下記コマンドでサーバーの動作確認をしてみた。
#!/bin/sh
echo '{"name": "hoge"}' | evans --proto greeter.proto -p 50051 cli call helloworld.Greeter.SayHello
echo '{"name": "hoge"}' | evans --proto greeter.proto -p 50051 cli call helloworld.Greeter.ShoutHello
おわりに
example がシンプルで良い感じだったので、すぐ動いた。example 大事。
もっと成熟した方法もある気がするので、そのうち試してみたい。
Discussion