gRPCとは?
Googleが開発したプロトコル
Protocol Buffers を使ってデータをシリアライズし、高速な通信を実現できる。
HTTP2.0を基盤としたRPC(リモートプロシージャコール)を実装するための技術
gRPCのメリット
- .protoというファイル形式でクライアント側、サーバー側でコードを自動生成できる
スキーマベースの開発となるため、連携のコミュニケーションが便利 - クライアント側では、RESTのようにエンドポイントを呼び出す代わりに、メソッドを呼び出すような形で実行ができる
- マイクロサービスアーキテクチャに向いている。主にサーバー間通信
- HTTPの詳細部分を意識する必要がない
- 処理が高速
- 双方向通信が簡単に実装できる
HTTP2.0とは?
こちらの記事が参考になるので貼っておきます
そろそろ知っておきたいHTTP/2の話
なにが良くなったかざっくりとまとめると
- リクエストの情報がテキストベースからシリアライズされたバイナリー形式になったことで解析時間が短くなった
- TCPコネクションを複数貼る必要がなく、一つ貼って並列に実行ができる
RPCとは?
別のサーバーにある関数やメソッドを、ローカルで関数を呼びだすのと同じように実行することができる
Node.jsを使って実際に実装してみた
事前準備
今回はTypescriptを使って書くので、以下のライブラリをインストール
$ npm install nodemon ts-node typescript
grpcを使うために必要なライブラリを
- grpc-tools
.protoファイルからコードを生成するためのコマンドを提供する - grpc_tools_node_protoc_ts
生成されたコードの型定義ファイル(.d.ts)も一緒に生成してくれる - @grpc/grpc-js
- google-protobuf
$ 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
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側の実装
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))