🦕

Deno 🦕 で gRPC 🦮 試してみた

2022/10/29に公開・約4,100字

はじめに

最近、少し 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
https://twitter.com/mattn_jp/status/1577329836506648577

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 を定義

greeter.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

こんなファイルが生成される。シンプルなインターフェースが入ってる。

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()で登録する感じ。オブジェクトのメソッドっぽく書かれてたのをクラスにしてみた。

server.ts
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 大事。
もっと成熟した方法もある気がするので、そのうち試してみたい。

GitHubで編集を提案

Discussion

ログインするとコメントできます