Protobuf Editionsの概要

Protobuf Editions機能の概要です。

Protobuf Editionsは、Protocol Buffersで使用されてきたproto2およびproto3の指定を置き換えます。proto定義ファイルの先頭に`syntax = "proto2"`や`syntax = "proto3"`を追加する代わりに、`edition = "2023"`のようなエディション番号を使用して、ファイルが持つデフォルトの動作を指定します。Editionsにより、言語は時間の経過とともに段階的に進化できるようになります。

以前のバージョンにあったハードコードされた動作の代わりに、エディションは機能ごとにデフォルト値(動作)を持つ機能の集合を表します。機能とは、protoc、コードジェネレーター、およびprotobufランタイムの動作を指定する、ファイル、メッセージ、フィールド、列挙型などに対するオプションです。選択したエディションのデフォルトの動作がニーズに合わない場合、これらの異なるレベル(ファイル、メッセージ、フィールドなど)で動作を明示的に上書きできます。また、上書きをさらに上書きすることも可能です。このトピックの後半にある語彙的スコープに関するセクションで、それについて詳しく説明します。

最新のリリース版は2023です。

機能のライフサイクル

Editionsは、機能のライフサイクルのための基本的な増分を提供します。機能には、導入、デフォルト動作の変更、非推奨化、そして削除という予想されるライフサイクルがあります。例として

  1. エディション2031は、`feature.amazing_new_feature`をデフォルト値`false`で作成します。この値は、以前のすべてのエディションと同じ動作を維持します。つまり、デフォルトでは影響はありません。

  2. 開発者は、.protoファイルを`edition = "2031"`に更新します。

  3. 後のエディション(例えばエディション2033)では、`feature.amazing_new_feature`のデフォルトが`false`から`true`に切り替わります。これはすべてのプロトにとって望ましい動作であり、protobufチームがこの機能を作成した理由でもあります。

    Prototillerツールを使用して以前のバージョンのprotoファイルをエディション2033に移行すると、以前の動作を維持し続けるために必要に応じて明示的な`feature.amazing_new_feature = false`エントリが追加されます。開発者は、新しい動作を.protoファイルに適用したいときに、これらの新しく追加された設定を削除します。

  1. ある時点で、`feature.amazing_new_feature`はあるエディションで非推奨とマークされ、後のエディションで削除されます。

    機能が削除されると、その動作のためのコードジェネレーターおよびそれをサポートするランタイムライブラリも削除される可能性があります。ただし、期間は十分にとられます。ライフサイクルの初期段階の例に倣うと、非推奨化はエディション2034で行われるかもしれませんが、削除は約2年後のエディション2036まで行われないでしょう。機能の削除は、常にメジャーバージョンアップを引き起こします。

このライフサイクルにより、非推奨の機能を使用しない`.proto`ファイルは、あるエディションから次のエディションへのno-opアップグレードとなります。Googleの移行期間と非推奨化期間を合わせて、コードをアップグレードするための十分な期間が与えられます。

前述のライフサイクルの例では機能にブール値を使用しましたが、機能は列挙型を使用することもできます。例えば、`features.field_presence`には`LEGACY_REQUIRED`、`EXPLICIT`、`IMPLICIT`の値があります。

Protobuf Editionsへの移行

Editionsは既存のバイナリを破損せず、メッセージのバイナリ、テキスト、またはJSONシリアライズ形式を変更しません。最初のEditionは、可能な限り最小限の破壊に留められています。最初のEditionはベースラインを確立し、proto2とproto3の定義を新しい単一の定義形式に統合します。

後続のエディションがリリースされると、機能のデフォルト動作が変更される可能性があります。Prototillerに.protoファイルのno-op変換を行わせることもできますし、新しい動作の一部またはすべてを受け入れることも選択できます。Editionsは、およそ年に一度のリリースが予定されています。

Proto2からEditionsへ

このセクションでは、proto2ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。

Proto2構文

// proto2 file
syntax = "proto2";

package com.example;

message Player {
  // in proto2, optional fields have explicit presence
  optional string name = 1 [default = "N/A"];
  // proto2 still supports the problematic "required" field rule
  required int32 id = 2;
  // in proto2 this is not packed by default
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // in proto2 enums are closed
  optional Handed handed = 4;

  reserved "gender";
}

Editions構文

// Edition version of proto2 file
edition = "2023";

package com.example;

option features.utf8_validation = NONE;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1 [default = "N/A"];
  // to match the proto2 behavior, LEGACY_REQUIRED is set at the field level
  int32 id = 2 [features.field_presence = LEGACY_REQUIRED];
  // to match the proto2 behavior, EXPANDED is set at the field level
  repeated int32 scores = 3 [features.repeated_field_encoding = EXPANDED];

  enum Handed {
    // this overrides the default edition 2023 behavior, which is OPEN
    option features.enum_type = CLOSED;
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

Proto3からEditionsへ

このセクションでは、proto3ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。

Proto3構文

// proto3 file
syntax = "proto3";

package com.example;

message Player {
  // in proto3, optional fields have explicit presence
  optional string name = 1 [default = "N/A"];
  // in proto3 no specified field rule defaults to implicit presence
  int32 id = 2;
  // in proto3 this is packed by default
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // in proto3 enums are open
  optional Handed handed = 4;

  reserved "gender";
}

Editions構文

// Editions version of proto3 file
edition = "2023";

package com.example;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1 [default = "N/A"];
  // to match the proto3 behavior, IMPLICIT is set at the field level
  int32 id = 2 [features.field_presence = IMPLICIT];
  // PACKED is the default state, and is provided just for illustration
  repeated int32 scores = 3 [features.repeated_field_encoding = PACKED];

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

語彙的スコープ

Editions構文は、機能ごとの許可されたターゲットのリストを持つ語彙的スコープをサポートしています。たとえば、最初のEditionでは、機能はファイルレベルまたは最も低い粒度レベルでのみ指定できます。語彙的スコープの実装により、ファイル全体にわたる機能のデフォルト動作を設定し、その後、メッセージ、フィールド、列挙型、列挙値、oneof、サービス、またはメソッドレベルでその動作を上書きできます。上位レベル(ファイル、メッセージ)で行われた設定は、同じスコープ内(フィールド、列挙値)で設定が行われていない場合に適用されます。明示的に設定されていない機能は、.protoファイルに使用されるエディションバージョンで定義された動作に準拠します。

次のコードサンプルは、ファイル、フィールド、および列挙型レベルで設定されているいくつかの機能を示しています。

edition = "2023";

option features.enum_type = CLOSED;

message Person {
  string name = 1;
  int32 id = 2 [features.field_presence = IMPLICIT];

  enum Pay_Type {
    PAY_TYPE_UNSPECIFIED = 1;
    PAY_TYPE_SALARY = 2;
    PAY_TYPE_HOURLY = 3;
  }

  enum Employment {
    option features.enum_type = OPEN;
    EMPLOYMENT_UNSPECIFIED = 0;
    EMPLOYMENT_FULLTIME = 1;
    EMPLOYMENT_PARTTIME = 2;
  }
  Employment employment = 4;
}

上記の例では、`presence`機能は`IMPLICIT`に設定されています。設定されていない場合は、デフォルトで`EXPLICIT`になります。`Pay_Type`の`enum`はファイルレベルの設定が適用されるため、`CLOSED`になります。しかし、`Employment`の`enum`は列挙型内で設定されているため、`OPEN`になります。

Prototiller

Prototillerツールがリリースされる際、エディションへの移行とエディション間の移行を容易にするために、移行ガイドと移行ツールを提供します。このツールにより、以下のことが可能になります。

  • proto2およびproto3定義ファイルを新しいエディション構文に大規模に変換する
  • あるエディションから別のエディションへファイルを移行する
  • その他の方法でprotoファイルを操作する

後方互換性

Protobuf Editionsは、可能な限り最小限の破壊となるように構築されています。例えば、proto2およびproto3の定義をエディションベースの定義ファイルにインポートしたり、その逆も可能です。

// file myproject/foo.proto
syntax = "proto2";

enum Employment {
  EMPLOYMENT_UNSPECIFIED = 0;
  EMPLOYMENT_FULLTIME = 1;
  EMPLOYMENT_PARTTIME = 2;
}
// file myproject/edition.proto
edition = "2023";

import "myproject/foo.proto";

proto2またはproto3からEditionsへ移行すると生成されるコードは変更されますが、ワイヤーフォーマットは変更されません。Editions構文のproto定義を使用して、proto2およびproto3のデータファイルやファイルストリームに引き続きアクセスできます。

文法の変更

Editionsには、proto2およびproto3と比較していくつかの文法変更があります。

構文の説明。 `syntax`要素の代わりに、`edition`要素を使用します

syntax = "proto2";
syntax = "proto3";
edition = "2028";

予約名。 フィールド名と列挙値名を予約する際に、引用符で囲む必要がなくなりました

reserved foo, bar;

グループ構文。 proto2で利用可能だったグループ構文は、Editionsでは削除されました。グループが使用していた特別なワイヤーフォーマットは、`DELIMITED`メッセージエンコーディングを使用することで引き続き利用できます。

必須ラベル。 proto2でのみ利用可能だった`required`ラベルは、Editionsでは利用できません。基になる機能は、`features.field_presence=LEGACY_REQUIRED`を使用することで引き続き利用できます。