gRPCで使う.protoファイルの書き方まとめ


- gRPCでは、Protocol Buffersのフォーマットでシリアライズしてデータのやり取りを行う。
- `.proto`ファイルにてスキーマ定義を行い、ツールを使ってコード生成ができる。そのため、クライアント・サーバーそれぞれでこの.protoファイルを共有できれば、仕様のズレなく開発をすすめることが可能。
- この記事では、.protoファイルの文法をざっとまとめてみる

## サンプル全体
- [GitHub](https://github.com/captain-blue210/starting-gRPC/blob/main/proto/sample.proto)

```proto
// バージョン定義
syntax = "proto3";

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

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

// サービスとRPCメソッド定義
service SampleService {
    rpc Sample (SampleRequest) returns (SampleResponse);

    // サーバーストリーミングRPC
    rpc SampleServerStreamMethod (SampleRequest) returns (stream SampleResponse);

    // クライアントストリーミングRPC
    rpc SampleClientStreamMethod (stream SampleRequest) returns (SampleResponse)

    // 双方向ストリーミングRPC
    rpc SampleBidirectionalMethod (stream SampleRequest) returns (stream SampleResponse)
}

message SampleRequest {
    string name = 1;
}

message SampleResponse {
    Sample sample = 1;
}


// メッセージ型
// 右の数字は「タグナンバー」
message Sample {
    // スカラー型
    // 数値、文字列、真偽値、バイト配列がある
    int32 id = 1;
    string name = 2;
    bool isBool = 3;

    // deprecated 廃止予定かつ非推奨であるフィールドを明示する
    string duplicated_field = 4 [deprecated = true]

    // reserved識別子 廃盤にしたタグナンバー
    reserved 7, 8, 10 to 12;

    // リスト（配列）
    // 多次元配列は定義できない
    repeated SampleList sample_list = 5;

    // マップ（連想配列）
    map<string, string> sample_map = 6;

    // 複数の中からどれかひとつ
    oneof message {
        string one = 1;
        string other = 2;
    }

    // Well Known Types
    google.protobuf.Duration sample_duration = 9;
    google.protobuf.Timestamp create_time = 13;

    // 列挙型
    enum SampleEnum {
        UNKNOWN = 0;
        TEST1 = 1;
        TEST2 = 2;
        TEST3 = 3;
    }

}
```

## バージョンの定義
```proto
syntax = "proto3";
```

## パッケージの定義
- 他の.protoファイルで定義したメッセージを使うときなどに、名前の衝突を避けるためにパッケージ名を設定できる

```proto
package sample;
```

## import
- 他の.protoファイルで定義したメッセージ型を使いたい場合に使う
- ここではGoogleがつくった.protoファイルをimportしている


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

## サービスとRPCメソッド
- APIの定義本体
- SampleServiceというサービスを定義
- SampleRequestを引数に取り、SampleResponseを返すSampleというメソッドを定義している
- ストリーミングにしたい場合は引数、戻り値にstreamをつける
- 単方向ストリーミング
    - サーバーストリーミングRPC（戻り値にstreamを設定）
        - サーバー側が複数のレスポンスを非同期で返す
    - クライアントストリーミングRPC（引数にstreamを設定）
        - クライアント側から複数のリクエストを非同期で送り、サーバー側は１つのレスポンスを返す
- 双方向ストリーミング
    - 引数、戻り値の両方にstreamをつける

```proto
service SampleService {
    rpc Sample (SampleRequest) returns (SampleResponse);

    // サーバーストリーミングRPC
    rpc SampleServerStreamMethod (SampleRequest) returns (stream SampleResponse);

    // クライアントストリーミングRPC
    rpc SampleClientStreamMethod (stream SampleRequest) returns (SampleResponse);

    // 双方向ストリーミングRPC
    rpc SampleBidirectionalMethod (stream SampleRequest) returns (stream SampleResponse);
}
```

## スカラー型
- 数値、文字列、真偽値、バイト配列がデータ型としてある
    - [データ型の詳細と各言語にコンパイルされたあとのデータ型の対応はこちら](https://developers.google.com/protocol-buffers/docs/proto3#scalar)
- デフォルト値

|データ型|デフォルト値|
|:-----|:----|
|string|空文字|
|bytes|空配列|
|bool|false|
|数値|0|
|enum|最初に定義された値。必ず0にする|
|メッセージ型|実装依存|
|repeated|空配列|

## メッセージ型
- 複数のフィールドを持った型
- タグナンバー
    - フィールドの右側の数字。
    - 同一メッセージ内で一意にしなければならない
    - reserved識別子
        - フィールド削除などで廃盤にしたタグナンバーを記載する
    - deprecatedオプション
        - 廃止予定かつ非推奨のフィールドを明示する

```proto
message Sample {
    // スカラー型
    // 数値、文字列、真偽値、バイト配列がある
    int32 id = 1;
    string name = 2;
    bool isBool = 3;

    // deprecated 廃止予定かつ非推奨であるフィールドを明示する
    string duplicated_field = 4 [deprecated = true]

    // reserved識別子 廃盤にしたタグナンバー
    reserved 7, 8, 10 to 12;

　　// 〜　中略　〜
}
```

## リスト（配列）
- 型の前にrepeatedをつけることで配列を定義できる
- スカラー型・メッセージ型ともに使用可能
- 多次元配列は定義できない

```proto
    // リスト（配列）
    // 多次元配列は定義できない
    repeated SampleList sample_list = 5;
```

## マップ（連想配列）
- キーには整数値、文字列、真偽値のみ使える
- mapは配列にできない

```proto
    map<string, string> sample_map = 6;
```


## 列挙型
- 型の前にenumをつけることで列挙型を定義できる

```proto
    // 列挙型
    enum SampleEnum {
        UNKNOWN = 0;
        TEST1 = 1;
        TEST2 = 2;
        TEST3 = 3;
    }
```

## oneof
- oneofをつけることで、複数のものから１つという定義ができる
- ここでは`one`か`other`を返すメッセージ型になる

```proto
    oneof message {
        string one = 1;
        string other = 2;
    }
```

## Well Known Types
- Googleが定義したメッセージ型
- 日時・期間や、戻り値を返さないことを表すものなどがある

```proto
    google.protobuf.Duration sample_duration = 9;
    google.protobuf.Timestamp create_time = 13;
```

## コードの生成
- 上記で定義した.protoファイルをもとに、クライアント・サーバーの両サイドで各言語用のprotocコマンドを使ってボイラープレートコードを生成する

## 参考
- [スターティングgRPC](https://tatsu-zine.com/books/starting-grpc)
- [Protocol Buffers：Language Guide (proto3)](https://developers.google.com/protocol-buffers/docs/proto3)