🔌

gRPCについて(Nodeをベースに)

2021/12/31に公開

実務でgRPCを使っているのですが、なんとなく使っていてあまり概要を調べられていなかったので、詳しく勉強してみました。

概要

gRPC は、RPC (Remote Procedure Call) を実現するためにGoogleが開発したプロトコルの1つです。Protocol Buffers を使ってデータをシリアライズし、高速な通信を実現できる点が特長です。

gRPCでは、IDL(インターフェース定義言語)を使ってあらかじめAPI仕様を .proto ファイルとして定義し、そこからサーバー側&クライアント側に必要なソースコードのひな形を生成します。言語に依存しないIDLで先にインタフェースを定義することで、様々なプログラミング言語の実装を生成できるというメリットがあります。

例えば、Pythonで作られているAIサーバとNodeで作られているAPIサーバがあったときに下記のような流れで開発できます。

  1. protoファイルにて値の受け渡しを定義する

  2. protoファイルをコンパイルして各言語のひな形コードを生成

  3. AI,APIサーバそれぞれで開発を進める

  4. protoファイルにて値の受け渡しを定義する

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// 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;
}

string型のnameという値を渡すと、string型のmessageが返ってくるということを定義している。

  1. protoファイルをコンパイルして各言語のひな形コードを生成

.pb.tsファイルに共通インターフェースが生成される

// package: helloworld
// file: proto/helloworld.proto

/* tslint:disable */
/* eslint-disable */

import * as jspb from "google-protobuf";

export class HelloRequest extends jspb.Message { 
    getName(): string;
    setName(value: string): HelloRequest;


    serializeBinary(): Uint8Array;
    toObject(includeInstance?: boolean): HelloRequest.AsObject;
    static toObject(includeInstance: boolean, msg: HelloRequest): HelloRequest.AsObject;
    static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
    static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
    static serializeBinaryToWriter(message: HelloRequest, writer: jspb.BinaryWriter): void;
    static deserializeBinary(bytes: Uint8Array): HelloRequest;
    static deserializeBinaryFromReader(message: HelloRequest, reader: jspb.BinaryReader): HelloRequest;
}

export namespace HelloRequest {
    export type AsObject = {
        name: string,
    }
}

export class HelloReply extends jspb.Message { 
    getMessage(): string;
    setMessage(value: string): HelloReply;


    serializeBinary(): Uint8Array;
    toObject(includeInstance?: boolean): HelloReply.AsObject;
    static toObject(includeInstance: boolean, msg: HelloReply): HelloReply.AsObject;
    static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
    static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
    static serializeBinaryToWriter(message: HelloReply, writer: jspb.BinaryWriter): void;
    static deserializeBinary(bytes: Uint8Array): HelloReply;
    static deserializeBinaryFromReader(message: HelloReply, reader: jspb.BinaryReader): HelloReply;
}

export namespace HelloReply {
    export type AsObject = {
        message: string,
    }
}
  1. AI,APIサーバそれぞれで開発を進める

デバックに関して

BloomRPCというGUIクライアントツールを使えば、デバックがやりやすかったです。
JSON 形式で記述したリクエストデータを実行中のサービスに送信し、レスポンスデータの内容を確認することができます。ストリームにも対応しています
RestでいうPostman近い感じです。
詳しくは下記のQiitaを参考にしてください。
https://github.com/bloomrpc/bloomrpc/blob/master/README.md

メリット

  • RESC API(OpenAPI)よりは記述がシンプル
  • .protoファイルをベースに開発を進められる → ドキュメントととして残していける
  • パラメータを型付けできる
  • HTTP/2を活かした高速な双方向ストリーミング通信が可能(これについてはまだ良くわかってません。とりあえず早く通信ができるとだけ覚えています

まとめ

個人的にスキーマーベース開発は気に入っていて、メンバー間でのコミュニケーションコストが下がって、よきせぬバグが起こりにくいかなと思っています。
特に分業制で開発しているチームにとっては恩恵は大きいのではないでしょうか?

■参考記事
https://engineering.mercari.com/blog/entry/20201216-53796c2494/

Discussion