gRPC 公式サンプルを Node.js + TypeScript on Windows (+ PowerShell) 化する

# 参考

こちらの素晴らしい記事を全面的に参考にしていきます。

https://qiita.com/daisaru11/items/67366061f7244378639c

# この記事でする事

- 参考記事の通りに進めると、Windows と lint 由来の躓きポイントやがあります。これに対応していきます。
- コマンドプロンプトは今後PowerShellに置き換えられていくそうなので、PowerShell前提で進めます。
- 題材として公式サイトにあるサンプルprotoファイルを使用して、これをTypeScript対応させていきます。

https://grpc.io/docs/quickstart/node/

# 下準備

適当な場所に、新規フォルダを作成し、``npm`` 初期化から ``typescript`` と ``ts-node`` のインストールまで行います。

```powershell
npm init -y
npm i -D typescript ts-node
```

今後 ``src`` フォルダにソースを入れていくので、``src`` フォルダも用意します。

# gRPC 実装

では、gRPC 用のモジュール追加やらファイル作成していきます。
どうでも良い話ですが、ずっとgr **pc** を gr **cp** だと思っていて、いまだに間違えます。P = Protocolの印象があるからですね…

## proto ファイルを作成

前述の通り公式サンプルを使用するので、以下のように proto ファイルを作成します。
今回は ``proto`` フォルダを作成してその中に ``hello.proto`` を作成しました。

```proto
syntax = "proto3";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

冒頭の ``syntax = "proto3";`` は、公式サイトには無いのでご注意下さい。

## コード生成

必要な依存関係をインストールします。

```powershell
npm i -D grpc-tools grpc_tools_node_protoc_ts
```

参考元の記事ではここでシェルスクリプトを書くのですが、こちとら Windows なので ``ps1`` ファイルです。
``proto.ps1`` というファイルを作成します。変数は必要であれば適宜変更してください。
windowsでは ``proto\*`` という記述が出来ないみたいで、とりあえず一つのファイルだけを指定しています。

今回はこれで問題ないのですが、おまけ2に複数ファイルを変換できるコマンドを記載しておきます。 

```powershell
$PROTO_SRC="proto\hello.proto"
$PROTO_DEST="$(pwd)\src"

npx grpc_tools_node_protoc --js_out=import_style=commonjs,binary:$PROTO_DEST  --grpc_out=$PROTO_DEST --plugin=protoc-gen-grpc=$(npm bin)/grpc_tools_node_protoc_plugin.cmd $PROTO_SRC
npx grpc_tools_node_protoc --plugin=protoc-gen-ts=$(npm bin)/protoc-gen-ts.cmd --ts_out=$PROTO_DEST $PROTO_SRC
```

ポイントは、コマンドに ``.cmd`` が追加されている事ですね。

これを実行します。

```powershell
./proto.ps1
```

すると、``src/proto`` に以下のファイルが出来ます。
``proto`` フォルダは自動で作成するようですが、``src`` フォルダは事前に作っておかないとエラーが出ます。

- hello_grpc_pb.d.ts
- hello_grpc_pb.js
- hello_pb.d.ts
- hello_pb.js

## サーバー実装

gRPC サーバーを実装するための依存関係は以下の通りです。

```powershell
npm i -D @types/google-protobuf
npm i -S grpc google-protobuf
```

``src/app.ts`` を作成し、以下の内容を記述します。

```ts
import * as grpc from "grpc";
import { IGreeterServer, GreeterService } from "./proto/hello_grpc_pb";
import { HelloRequest, HelloReply } from "./proto/hello_pb";

class HelloService implements IGreeterServer {
  public sayHello(
    call: grpc.ServerUnaryCall<HelloRequest>,
    callback: grpc.sendUnaryData<HelloReply>
  ): void {
    const r = new HelloReply();
    r.setMessage("Hello " + call.request.getName());
    console.log("receive: " + call.request.getName())
    callback(null, r);
  }
}

const server = new grpc.Server();
server.bind("0.0.0.0:50051", grpc.ServerCredentials.createInsecure());
server.addService(GreeterService, new HelloService());

server.start();
console.log("server running on 0.0.0.0:50051");
```

いずれもタイプセーフ、lintにも引っかかりません。気持ちいい。
以下のコマンドでサーバー起動します。

```powershell
npx ts-node src/app.ts
```

## クライアント実装

同様に、クライアントも実装していきます。
``src/client.ts`` を作成し、以下の内容を記述します。

```ts
import * as grpc from "grpc";
import { GreeterClient } from "./proto/hello_grpc_pb";
import { HelloRequest } from "./proto/hello_pb";

const client = new GreeterClient(
  "127.0.0.1:50051",
  grpc.credentials.createInsecure()
);

const req = new HelloRequest();
req.setName("John");

client.sayHello(req, function (error, result) {
  if (error) console.log("Error: ", error);
  else console.log(result.toObject());
});
```

こちらもタイプセーフでlintクリア。気持ちいい。
以下のコマンドで起動します。

```powershell
npx ts-node src/client.ts
```

## テスト

PowerShell のプロンプトを2つ立ち上げ、サーバー -> クライアントの順に起動します。
サーバー側に

```powershell
server running on 0.0.0.0:50051
receive: John
```

クライアント側に

```powershell
{ message: 'Hello John' }
```

が表示されれば成功です。

# おまけ: 必要な依存パッケージまとめ

必要な依存関係が、記事中にバラバラしてしまったので、まとめておきます。

```powershell
npm i -D typescript ts-node grpc-tools grpc_tools_node_protoc_ts @types/google-protobuf
npm i -S grpc google-protobuf
```

# おまけ2: 複数のprotoファイルを変換する

前述の通り windows の PowerShell でワイルドカード指定が出来ないので、ファイル一覧を取得して一つ一つ変換するコマンドを書きました。ゴリ押しです。

```powershell
$PROTO_DIR="proto"
$PROTO_DEST="$pwd\src"

$LIST=Get-ChildItem ${pwd}/$PROTO_DIR -Name -Include *.proto

function convert($FILE) {
	npx grpc_tools_node_protoc --js_out=import_style=commonjs,binary:$PROTO_DEST  --grpc_out=$PROTO_DEST --plugin=protoc-gen-grpc=$(npm bin)/grpc_tools_node_protoc_plugin.cmd $PROTO_DIR/$FILE
	npx grpc_tools_node_protoc --plugin=protoc-gen-ts=$(npm bin)/protoc-gen-ts.cmd --ts_out=$PROTO_DEST $PROTO_DIR/$FILE
}

foreach($item in $LIST) {
    convert($item); 
}
```