サーバー間通信を行うのでgrpcを導入してみた

# gRPCとは？
Googleが開発したプロトコル
Protocol Buffers を使ってデータをシリアライズし、高速な通信を実現できる。
HTTP2.0を基盤としたRPC(リモートプロシージャコール)を実装するための技術

# gRPCのメリット
- .protoというファイル形式でクライアント側、サーバー側でコードを自動生成できる
スキーマベースの開発となるため、連携のコミュニケーションが便利
- クライアント側では、RESTのようにエンドポイントを呼び出す代わりに、メソッドを呼び出すような形で実行ができる
- マイクロサービスアーキテクチャに向いている。主にサーバー間通信
- HTTPの詳細部分を意識する必要がない
- 処理が高速
- 双方向通信が簡単に実装できる

# HTTP2.0とは？
こちらの記事が参考になるので貼っておきます
[そろそろ知っておきたいHTTP/2の話](https://qiita.com/mogamin3/items/7698ee3336c70a482843)
なにが良くなったかざっくりとまとめると
- リクエストの情報がテキストベースからシリアライズされたバイナリー形式になったことで解析時間が短くなった
- TCPコネクションを複数貼る必要がなく、一つ貼って並列に実行ができる

# RPCとは？
別のサーバーにある関数やメソッドを、ローカルで関数を呼びだすのと同じように実行することができる

# Node.jsを使って実際に実装してみた

### 事前準備
今回はTypescriptを使って書くので、以下のライブラリをインストール

```sh
$ npm install nodemon ts-node typescript
```

### grpcを使うために必要なライブラリを
- grpc-tools
.protoファイルからコードを生成するためのコマンドを提供する
- grpc_tools_node_protoc_ts
生成されたコードの型定義ファイル(.d.ts)も一緒に生成してくれる
- @grpc/grpc-js
- google-protobuf

```sh
$ npm install grpc-tools grpc_tools_node_protoc_ts --save-dev
$ npm install @grpc/grpc-js google-protobuf
```

### .protoファイルの生成
packageは、別の.protoファイルでmessageのなどを利用する際に必要になる


```.proto
syntax = "proto3";
package company;

service Company {
  rpc search (SearchRequest) returns (SearchResponse) {}
}

message SearchRequest {
  string text = 1;
}

message SearchResponse {
  repeated CompanyModel companies = 1;
}

message CompanyModel {
  string companyName = 1;
  bool isVenture = 2;
  bool isLarge = 3;
}
```

### コードの生成

```
$ grpc_tools_node_protoc --plugin=protoc-gen-ts=node_modules/.bin/protoc-gen-ts --js_out=import_style=commonjs,binary:gen --grpc_out=grpc_js:gen --ts_out=grpc_js:gen -I ./src ./src/proto/*.proto
```

gen/proto以下にこのようなファイルが生成される
![スクリーンショット 2021-06-25 8.50.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114212/d40e4003-cc3f-9cf4-8b35-65a19f3ec196.png)


### server側の実装
```.ts
import { sendUnaryData, ServerUnaryCall } from "@grpc/grpc-js";
import { SearchRequest, SearchResponse } from "../../../gen/proto/Company_pb";
export async function companyGrpcSearch(
    call: ServerUnaryCall<SearchRequest, SearchResponse>,
    callback: sendUnaryData<SearchResponse>
) {
    const response = new SearchResponse();
    const text = call.request.getText();
    response.setCompaniesList([])
    if (true) {
        callback(null, response);
    } else {
        // エラーを返す
        callback({code: Status.NOT_FOUND, details: 'あてはまる企業が存在しません'})
    }
}
```

### client側の実装
```ts
import { credentials } from "@grpc/grpc-js";
import { CompanyClient } from "../gen/proto/Company_grpc_pb";
import {SearchRequest} from '../gen/proto/Company_pb';

const serverURL = `localhost:8080`;

export const grpcClientOptions = {
    "grpc.lb_policy_name": "round_robin",
    "grpc.dns_min_time_between_resolutions_ms": 5000,
    "grpc.keepalive_timeout_ms": 1000,
};

export type RequestParams = {
  text: string;
};
export async function search({ text = "" }: RequestParams) {
  const Request = new SearchRequest();
  const Client = new CompanyClient(
    serverURL,
    credentials.createInsecure(),
    grpcClientOptions
  );
  Request.setText(text);

  return new Promise((resolve, reject) => {
    Client.search(Request, (error, response) => {
      if (error) {
        console.error(error);
        reject({
          code: error?.code || 500,
          message: error?.message || "something went wrong",
        });
      }

      return resolve(response.toObject());
    });
  });
}

search({text: 'test'}).then((response) => console.log(response))
```