Protocol Buffers入門:.protoファイルの書き方と型・オプションまとめ

スポンサーリンク

Protocol Buffers(protobuf)はGoogleが開発したデータシリアライズフォーマットです。JSONやXMLに比べてバイナリ形式のため高速・軽量で、gRPCのインターフェイス定義としても使われます。.proto ファイルに型を定義するとコンパイラ(protoc)が各言語のコードを生成します。

JSONとの比較

項目 JSON Protocol Buffers
形式 テキスト バイナリ
サイズ 大きい 3〜10倍小さい
速度 普通 高速
可読性 高い 低い(バイナリ)
スキーマ 任意 .proto で必須定義
後方互換性 設計次第 タグ番号で保証しやすい

.proto ファイルの基本構造

syntax = "proto3";          // バージョン指定(必須)

package myapp;              // パッケージ名(名前衝突を防ぐ)

option go_package = "./pb"; // 言語固有のオプション

message User {
  int32  id    = 1;
  string name  = 2;
  string email = 3;
}

スカラー型

proto型 Go Python Java 備考
double float64 float double 64bit浮動小数点
float float32 float float 32bit浮動小数点
int32 int32 int int 負の数には非効率
int64 int64 int long 負の数には非効率
uint32 uint32 int int 符号なし32bit
uint64 uint64 int long 符号なし64bit
sint32 int32 int int 負の数に効率的
sint64 int64 int long 負の数に効率的
bool bool bool boolean
string string str String UTF-8エンコード
bytes []byte bytes ByteString 任意バイト列

タグ番号のルール

フィールドに付く = 1= 2 はタグ番号(フィールド番号)です。

message User {
  int32  id    = 1;
  string name  = 2;
  string email = 3;
}
  • バイナリエンコード時の識別子として使われる
  • 一度リリースしたら変更・再利用してはいけない(後方互換性が壊れる)
  • 1〜15は1バイト、16〜2047は2バイトで表現される(よく使うフィールドは小さい番号に)
  • 19000〜19999はprotobufが予約しているため使用不可

repeated(配列)

message ListUsersResponse {
  repeated User   users = 1;
  repeated string tags  = 2;
}

Go では []User、Python では list に対応します。

map(辞書型)

message Config {
  map<string, string> labels = 1;
  map<string, int32>  scores = 2;
}
  • キーに使える型:整数型・boolstring(浮動小数点・bytesmessage は不可)
  • 値にはほぼすべての型が使える
  • 順序は保証されない

ネストしたメッセージ

message Order {
  int32            id      = 1;
  User             user    = 2;
  repeated OrderItem items  = 3;
}

message OrderItem {
  int32 product_id = 1;
  int32 quantity   = 2;
  float price      = 3;
}

メッセージ内にメッセージを定義することもできます。

message Order {
  message Item {
    int32 product_id = 1;
    int32 quantity   = 2;
  }
  int32         id    = 1;
  repeated Item items = 2;
}

enum(列挙型)

enum Status {
  STATUS_UNSPECIFIED = 0;  // proto3では0始まりが必須
  STATUS_ACTIVE      = 1;
  STATUS_INACTIVE    = 2;
  STATUS_DELETED     = 3;
}

message User {
  int32  id     = 1;
  string name   = 2;
  Status status = 3;
}

proto3では enum の最初の値は必ず 0 にする必要があります。慣習として XXX_UNSPECIFIED = 0 と書くことでデフォルト・未設定を明示します。

oneof(排他的フィールド)

複数フィールドのうち最大1つだけ値を持つ場合に使います。

message Notification {
  string title = 1;
  oneof content {
    string text_message  = 2;
    string html_message  = 3;
    bytes  image_content = 4;
  }
}

設定すると他のフィールドはクリアされます。

import(他のファイルを読み込む)

// user.proto
syntax = "proto3";
package myapp;

message User {
  int32  id   = 1;
  string name = 2;
}
// order.proto
syntax = "proto3";
package myapp;

import "user.proto";

message Order {
  int32 id   = 1;
  User  user = 2;
}

well-known types(Googleが用意した標準型)

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

message Event {
  string                    name       = 1;
  google.protobuf.Timestamp created_at = 2;
}

// 引数なし・戻り値なし
rpc DeleteAll (google.protobuf.Empty) returns (google.protobuf.Empty);

主なwell-known types:

用途
Timestamp 日時(Unix時刻)
Duration 期間
Empty 引数・戻り値なし
Any 任意のメッセージ
Int32Value / StringValue など null と未設定を区別するラッパー

デフォルト値

proto3ではフィールドに値を設定しない場合、型ごとのデフォルト値になります。

デフォルト値
数値型 0
bool false
string ""
bytes 空バイト列
enum 最初の値(0 の値)
message nil / null
repeated 空のリスト

null と未設定を区別したい場合は optional キーワードを使います。

message User {
  optional string nickname = 4;  // null と "" を区別できる
}

フィールドの削除と予約(reserved)

リリース済みのフィールドを削除するときは reserved でタグ番号と名前を予約します。これにより後から同じ番号・名前を誤って再利用することを防げます。

message User {
  reserved 4, 5;
  reserved "old_field";

  int32  id    = 1;
  string name  = 2;
  string email = 3;
  // 4と5は削除済み
}

JSON との相互変換

Go

import "google.golang.org/protobuf/encoding/protojson"

// proto → JSON
jsonBytes, _ := protojson.Marshal(user)
fmt.Println(string(jsonBytes))
// {"id":1,"name":"田中","email":"tanaka@example.com"}

// JSON → proto
user := &pb.User{}
protojson.Unmarshal(jsonBytes, user)

Python

from google.protobuf import json_format

# proto → JSON
json_str = json_format.MessageToJson(user)

# JSON → proto
user = json_format.Parse(json_str, pb.User())

まとめ

構文 用途
message データ型の定義
repeated 配列フィールド
map<K, V> 辞書フィールド
enum 列挙型
oneof 排他的フィールド
import 他の .proto を読み込む
reserved 削除済みフィールドを保護
optional null と未設定を区別する
  • タグ番号は変更・再利用しない(後方互換性の要)
  • フィールドを削除するときは reserved で保護する
  • 頻繁に使うフィールドはタグ番号1〜15に割り当てる(エンコードサイズ節約)

gRPCでのサービス定義と実装方法は「gRPC入門:Protocol Buffersの定義からサーバー・クライアント実装まで」を参照してください。