gRPCあれこれ

RPCとは

・Remote Procedure Call(遠隔手続き呼び出し)
 あるサービスから別のサービスの
 アプリケーションの処理(サブルーチン・クラス・関数など)を呼び出す技術。
 RPCを使うことで、違うアプリケーションのロジックを
 あたかも自分のアプリケーションの中に実装されているかのように扱うことができる。
 RPCでよく使われる技術には、gRPC・JSON-RPC・SOAPApache Thriftなどがある。

gRPCとは

Googleが開発した高速なAPI通信とスキーマ駆動開発を実現するRPCフレームワーク

REST

・RESTはリソース思考を強く打ち出している
 リソース志向はリソース(オブジェクト)を中心に考え、
 これに対してHTTPメソッドで操作していく考え。
 RPCではメソッドの呼び出しが基点となり、
 データはあくまでその副産物であるため考え方としては逆になる。
 RESTは規格が厳格に決められたものではなく、
 シンプルでスケーラブルなAPIを作るための設計原則。
 そのため、RESTでは原則に沿って自分で仕様を決めて実装することが求められる。
 一方でRPCフレームワークは、
 規格や仕様に沿って実装されたライブラリやフレームワークとして提供される。

特徴

・HTTP/2による高速な通信
・Protocol Buffers
・柔軟なストリーミング形式

HTTP/2

gRPCではHTTP/2のプロトコル上で通信が行われる。
HTTP/2では通信時にデータがテキストではなく、
バイナリにシリアライズされて送られる。
そのため小さな容量で転送でき、
ネットワーク内のリソースをより効率的に使用することができる。
またHTTP/2では一つのコネクションで
複数のリクエスト・レスポンスをやりとりできる。
そのためgRPCでもコネクションは常時張られっぱなしの状態になる。
リクエストのたびに接続と切断を行う必要がなく、
またヘッダーを都度おくる必要がないためより効率的な通信になる。

Protocol Buffers

gRPCではProtocol Buffersのフォーマットにシリアライズしてデータをやり取りする。
Protocol BuffersもgRPCと同様にGoogleが開発。
一番の特徴は、.protoファイルというIDL(インタフェース記述言語)。
.protoファイルを書いて、コンパイラを実行すると
任意の言語のサーバ・クライアント用コードを自動生成する。
そのため自分でAPIインタフェースを実装したり、
シリアライズされたデータのエンコード・デコード処理を書く必要がない。
gRPCではスキーマが最初に書かれるため
.protoファイルを見ればAPIの仕様は常に明確。

柔軟なストリーミング形式

・シンプルなRPC
・サーバーストリーミングRPC
・クライアントストリーミングRPC
・双方向ストリーミングRPC

protoファイル
// バージョン
syntax = "proto3";

// パッケージ定義
package myapp;

// サービス定義
service AddressBookService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

message SearchRequest {
  string name = 1;
}

message SearchResponse {
  Person person = 1;
}

message Person {
  int32 id = 1;
  string name = 2;
  string email = 3;
  // リスト(配列)
  repeated PhoneNumber phone_number = 4;

  // 列挙型(先頭は必ず0)
  enum PhoneType {
    UNKNOWN = 0;
    MOBILE = 1;
    HOME = 2;
    WORK = 3;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType phone_type = 2;
  }
}

※メッセージ型は各言語のコードに自動生成された際に、
構造体やクラスとして書き出される。

・マップ(連想配列
key_typeにキーとなる型を、value_typeに値となる型を定義

map<key_type, value_type> map_field = N;

// 文字列をキーとしたAddressBookを格納させたい場合
message Person {
  int32 id = 1;
  map<string, AddressBook> address_books = 2;
}

・oneof
フィールドの先頭にoneofと付与することで、
複数の型の中からどれかひとつという定義を行える。
※oneofはrepeatedにすることができず、
oneofの中でもrepeatedを使うことはできない。

message GreetingCard {
  int32 id = 1;
  oneof message {
    string text = 2;
    Image image = 3;
    Video video = 4;
  }
}

message Photo {
  ...
}

message Video {
  ...
}

・日時
google.protobuf.Durationは期間を表す型

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

message Person {
  int32 id = 1;
  google.protobuf.Duration apart_duration = 2;
google.protobuf.Timestamp create_time = 3;
google.protobuf.Timestamp update_time = 4;
}

※特に値を返す必要がない場合

import "google/protobuf/empty.proto";

Service AddressBookService {
  rpc Delete(DeleteRequest) google.protobuf.Empty
}
ストリーミングRPCの場合のメソッド定義
// サーバストリーミングRPC
rpc SearchHello (SearchRequest) returns (stream SearchResponse);

// クライアントストリーミングRPC
rpc SearchHello (stream SearchRequest) returns (SearchResponse);

// 双方向ストリーミングRPC
rpc SearchHello (stream SearchRequest) returns (stream SearchResponse);
proto作成
mkdir -p proto
touch proto/hello.proto
syntax = "proto3";
package hello;
option go_package = "./pb";

import "google/protobuf/timestamp.proto";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message Greet {
  enum GreetingType {
    UNKNOWN = 0;
    GOODMORNING = 1;
    HI = 2;
    HELLO = 3;
  }

  string name = 1;
  Greet greet = 2;
  float smile_score = 3;
  google.protobuf.Timestamp create_time = 15;
}

message Report {
  message GreetCount {
    Greet.GreetingType greet = 1;
    int32 count = 2;
  }
  repeated GreetCount greet_counts = 1;
}

message HelloRequest {
  Greet.GreetingType greet = 1;
}

message HelloResponse {
  Greet greet = 1;
}
コード生成

必要なプラグインのインストール

Quick start | Go | gRPC

mkdir -p proto/go
protoc --go_out=proto/go --go-grpc_out=proto/go proto/*.proto
grpc_cli ls localhost:3000 パッケージ名.サービス名 -l