開発者ガイド

protocol buffers の開発者ガイドへようこそ。 protocol buffers は、言語中立でプラットフォーム中立な、 通信プロトコルやデータ保存などで構造化データを扱うための拡張可能な直列化方式です。

このドキュメントは自身のアプリケーションで protocol buffers を利用したい Java, C++, Python 開発者に向けて書かれています(訳注: C++のみ訳出しています)。 この摘要はあなたに protocol buffers の案内となり、どこから始めればいいのか理解できるでしょう。 C++チュートリアルへ進んでもよいですし、エンコーディングの説明へ進んでより深く探究してもよいでしょう。 3つの言語には APIリファレンスが提供されていますし、同じく .proto ファイルを書くためには 言語ガイドスタイルガイド があります。

protocol buffers とは何か?

protocol buffers は、柔軟で効率的な、構造化データの自動直列化方式です。 XML を、小さく速く簡潔にしたようなものだと思ってください。 データをどのような構造にするかを一度定義すれば、 様々な言語向けに生成されたソースコードを使って、この構造化データを様々なデータストリームへ読み書きすることができます。

どのように動作するのか?

直列化したい情報をどのように構造化するかを .proto ファイルに定義します。 個々の protocol buffers メッセージは、一連の名前・値ペアを含んだ情報の小さな論理レコードです。 人物情報を含むメッセージを定義した .protoファイルのとても簡単な例をここに挙げましょう:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

見て分かるように、メッセージ形式はごく単純です ― それぞれのメッセージ型は一つ以上のユニークな番号の付いたフィールドを持ちます。そしてそれぞれのフィールドは名前と型を持ちます。 型には数値(整数か浮動小数点数)、真偽値、文字列、生バイト列、もしくはデータを階層的構造にできるように(上の例にあるように)他の protocol buffer メッセージ型を使えます。 また、オプションのフィールド、必須のフィールド、繰り返しフィールドの指定ができます。 .proto ファイルの記述に関するさらなる情報は Protocol Buffer 言語ガイド で見つけることができます。

メッセージを定義したら、アプリケーションの言語用に protocol buffers コンパイラを .proto ファイルに適用し、データアクセスクラスを生成します。 これらのクラスは、個々のフィールドに対して(query() や set_query() のような)単純なアクセサを提供します。 また同様に、構造体の全体を生バイト列に直列化/解析するメソッドも提供します。 例えば、C++言語の場合には、上述のサンプルコードをコンパイラにかけると Person というクラスを生成します。 アプリケーションからこのクラスを使い、protocol buffers の Person メッセージの展開、直列化、検索をすることができます。アプリケーションでは次のようなコードを書くことになるでしょう:

  Person person;
  person.set_name("John Doe");
  person.set_id(1234);
  person.set_email("jdoe@example.com");
  fstream output("myfile", ios::out | ios::binary);
  person.SerializeToOstream(&output);

後からこのメッセージを読み戻すことができます:

  fstream input("myfile", ios::in | ios::binary);
  Person person;
  person.ParseFromIstream(&input);
  cout << "Name: " << person.name() << endl;
  cout << "E-mail: " << person.email() << endl;

後方互換性を損なわずにメッセージ形式にフィールドを追加することができます。 古いバイナリは、解析時に単に新フィールドを無視します。 データフォーマットに protocol buffers を用いた通信プロトコルがあれば、 既存のコードが使えなくなる心配をせずにプロトコルを拡張できます。

生成された protocol buffers のコードの完全なリファレンスは、APIリファレンスの節で見つけることができます。 また、protocol buffers のメッセージがどのように符号化されるかについては、Protocol Buffers Encoding で見つけられます。

なぜ単に XML を使わないのですか?

構造化データを直列化するのには、procotol buffers は XML より多くの長所を持っています。
  • 単純
  • 3分の1 から 10分の1 ほど小さい
  • 20倍から 100倍速い
  • 曖昧さが少ない
  • 生成コードをプログラミングで使うのが簡単

例として、name と email がある person のモデルで説明しましょう。 XML では以下の記述をする必要があります:

  <person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
  </person>
対応する protocol buffers メッセージ(protocol buffers のテキスト形式)は:
# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
  name: "John Doe"
  email: "jdoe@example.com"
}
となります。

このメッセージが protocol buffers の バイナリ形式にエンコードされると(上のテキスト形式は、可読的でデバッグと編集に便利なだけのもの)、おおよそ 28バイトの長さで、解析には 100-200ナノ秒ほどかかります。 XML版は、空白を除いても少なくとも 69バイトになり、解析に5000-10000ナノ秒かかるでしょう。

さらに、protocol buffers の取扱いはとても簡単です:
  cout << "Name: " << person.name() << endl;
  cout << "E-mail: " << person.email() << endl;
XML では次のようにしなければならないでしょう:
  cout << "Name: "
       << person.getElementsByTagName("name")->item(0)->innerText()
       << endl;
  cout << "E-mail: "
       << person.getElementsByTagName("email")->item(0)->innerText()
       << endl;

しかしながら、protocol buffers は常に XML より優れた解決策であるとは限りません。 例えば、構造体とテキストを合わせて使うのが簡単ではないので、にテキストベースのマークアップ文書(例えばHTML)のひな型とするには良い方法ではありません。 さらに、XML は人間に読みやすくて書きやすいですが、少なくとも protocol buffers のネイティブ形式はそうではありません。 XML はまた自己記述的ですが、protocol buffers はメッセージ定義(.protoファイル)がある限りにおいて意味を持ちます。

これこそ私の求めていたソリューションみたいだ! 何から始めればいい?

パッケージをダウンロードします。 これには Java, Python, C++ 用の protocol buffers コンパイラとI/Oとテストをするのに必要なクラスの 完全なソースコードが含まれています。 コンパイラをビルドしインストールするためには、README の説明に従ってください。

全て揃ったら、使う言語用の チュートリアルを試してください。 チュートリアルでは、protocol buffers を使った簡単なアプリケーションを少しずつ作っていきます。

歴史を少々

protocol buffers は、Google で最初にインデックスサーバーへのリクエスト/レスポンスのプロトコルを扱うために開発されました。 protocol buffers 以前は、リクエストとレスポンスの marshalling/unmarshalling 処理を手で書くための、リクエストとレスポンスのフォーマットがありました。 そしてそれは複数のバージョンのプロトコルをサポートしていました。 このことは、以下のような醜悪なコードをもたらしました:

 if (version == 3) {
   ...
 } else if (version > 4) {
   if (version == 5) {
     ...
   }
   ...
 }

明確に定義されたプロトコルもまた、プロトコルの新たなバージョンを公開するのを難しくしました。 開発者は、新しいプロトコルを使うように切り替える前に、リクエスト送信元とリクエストを扱う実際のサーバーの全てが、新しいプロトコルを理解することを確実にしなければなりませんでした。

protocol buffers はこれらの問題の多くを解決するように設計されました:
  • 新しいフィールドの導入が簡単にできるように。 データの中身を見る必要のない中間サーバーが、全てのフィールドを知らずとも、そのデータの解析と通過をできるように。

  • フォーマットはできるだけ自己記述的にし、(C++, Java, 他の)様々な言語で扱えるように。

しかしながら、ユーザーは依然として自前の解析コード手書きする必要がありました。 システムが進化するにつれて、これは多数の機能と用途を獲得していきました:
  • 解析処理を手書きする必要性を避けるための、直列化/解析コードの自動生成

  • 短い寿命しかない RPC (Remote Procedure Call) リクエストだけでなく、 人々は protocol buffers をデータを永続的に保存するための手軽な自己記述形式(例えば Bigtable)として使い始めた。

  • protocol コンパイラが、サーバーインターフェースの実装を継承して記述するのに使う stub クラスを生成するようになり、 サーバー側 RPC インターフェースがプロトコルファイルの一部として宣言され始めた。

protocol buffers は今やデータ記述での Google の共通語となりました - この文書を書いている時点で、Google のコードツリーでは 12,183 の .proto ファイルに 48,162 の異なるメッセージ型が定義されています。 これらは RPCシステムと、様々なストレージシステムへのデータ永続化の両方に使われています。