Nest.js ではアプリケーションサーバとして動かすだけでなく、同時に gRPC として動かし、別のマイクロサービスと通信ができる設計になっている。
main.ts
(async () => { const app = await NestFactory.create(AppModule); const protoDir = join(__dirname, '..', 'protos'); app.connectMicroservice({ transport: Transport.GRPC, options: { url: '0.0.0.0:5000', package: 'rpc', protoPath: '/rpc/rpc.proto', loader: { keepCase: true, longs: Number, defaults: false, arrays: true, objects: true, includeDirs: [protoDir], }, }, }); await app.startAllMicroservicesAsync(); await app.listen(3000); });
普通のアプリケーションサーバとして動かす必要がないなら await app.listen(3000);
をコメントアウトする。
マイクロサービスなので単一の .proto
ファイルを読み込むレベルのシンプルなもので事足りると思うが、複数 proto を読み込みたい場合は includeDirs
でディレクトリを指定すればよい。ここは Nest.js 公式のサンプルになく、Issue あげたら教えてもらえた。この部分は @grpc/proto-loader が使われている。
rpc.controller.ts
Controller はデコレーターで gRPC のサービス名とメソッド名を指定する方式。
リクエスト/レスポンスの型情報は公式の grpc-tools
を使うより protobuf.js
で生成した方が JavaScript オブジェクトをそのまま型変換できて便利。
@Controller() export class RpcController { constructor(private readonly championService: ChampionService, private readonly battleFieldService: BattleFieldService) {} @GrpcMethod('Rpc', 'GetChampion') async getChampion(req: GetChampionRequest): Promise<GetChampionResponse> { const obj = this.championService.getChampion(req.champion_id); return GetChampionResponse.create({champion: obj}); } @GrpcMethod('Rpc', 'ListChampions') async listChampions(req: IEmpty): Promise<ListChampionsResponse> { const champions = this.championService.listChampions(); return ListChampionsResponse.create({champions}); } @GrpcMethod('Rpc', 'GetBattleField') async getBattleField(req: IEmpty): Promise<rpc.GetBattleFieldResponse> { const battleField = this.battleFieldService.getBattleField(); return GetBattleFieldResponse.create({battle_field: battleField}); } }
ここで必要なのは単純な型情報だけなので、自分で get-champion-request.dto.ts
みたいな interface を定義して使ってもいい。リクエストに含まれるパラメータが Nest.js が自動で格納するので、req.champion_id
のように参照することができる(keepCase: false
にするとキャメルケースになる)。
rpc.proto
protobuf のサービス定義はこんな感じ。サンプルでしかないけど League of Legends のチャンピオン取得ができる API みたいなのを想定している。
service Rpc { rpc GetChampion (GetChampionRequest) returns (GetChampionResponse); rpc ListChampions (Empty) returns (ListChampionsResponse); rpc GetBattleField (Empty) returns (GetBattleFieldResponse); }
疎通テスト
疎通確認には golang の gRPCurl を使うと便利。
$ grpcurl -d '{"champion_id": 1}' -plaintext -proto ./rpc/rpc.proto -import-path ./protos 127.0.0.1:5000 rpc.Rpc/GetChampion { "champion": { "championId": 1, "type": "ASSASSIN", "name": "Akali", "message": "If you look dangerous, you better be dangerous." } }
フルソースコード
Node.js の gRPC サンプルはあまりないので参考になるとよいです。