拡張機能の宣言
はじめに
このページでは、拡張宣言とは何か、なぜ必要なのか、そしてどのように使うのかを詳細に説明します。
注意
Proto3は拡張機能をサポートしていません(カスタムオプションの宣言を除く)。拡張機能はproto2とエディションでは完全にサポートされています。拡張機能の概要については、こちらの拡張機能ガイドをお読みください。
動機
拡張宣言は、通常のフィールドと拡張機能の間のちょうど良いバランスを目指しています。拡張機能と同様に、フィールドのメッセージ型への依存関係を回避し、その結果、未使用のメッセージを削除するのが難しい、または不可能な環境では、よりスリムなビルドグラフとより小さなバイナリになります。通常のフィールドと同様に、フィールド名/番号は囲むメッセージに表示され、競合を回避し、宣言されたフィールドの便利なリストを見やすくします。
拡張宣言によって占有されている拡張番号をリストアップすることで、ユーザーは利用可能な拡張番号を選択し、競合を回避しやすくなります。
使用方法
拡張宣言は拡張範囲のオプションです。C++の前方宣言と同様に、完全な拡張定義を含む`.proto`ファイルをインポートせずに、拡張フィールドのフィールド型、フィールド名、カーディナリティ(単数または繰り返し)を宣言できます。
edition = "2023";
message Foo {
extensions 4 to 1000 [
declaration = {
number: 4,
full_name: ".my.package.event_annotations",
type: ".logs.proto.ValidationAnnotations",
repeated: true },
declaration = {
number: 999,
full_name: ".foo.package.bar",
type: "int32"}];
}
この構文は以下のセマンティクスを持ちます
- 範囲のサイズが許せば、単一の拡張範囲内で、異なる拡張番号を持つ複数の`declaration`を定義できます。
- 拡張範囲に何らかの宣言がある場合、その範囲の_すべて_の拡張も宣言されなければなりません。これにより、未宣言の拡張が追加されるのを防ぎ、新しい拡張が範囲の宣言を使用することを強制します。
- 与えられたメッセージ型(`.logs.proto.ValidationAnnotations`)は、以前に定義またはインポートされている必要はありません。私たちは、それが別の`.proto`ファイルで定義される可能性のある有効な名前であることをチェックするだけです。
- この、または別の`.proto`ファイルが、この名前または番号でこのメッセージ(`Foo`)の拡張を定義するとき、私たちは、拡張の番号、型、および完全な名前がここで前方宣言されたものと一致することを強制します。
警告
`extensions 4, 999`のような拡張範囲グループの宣言は避けてください。どの拡張範囲に宣言が適用されるかが不明確であり、現在サポートされていません。拡張宣言は、異なるパッケージを持つ2つの拡張フィールドを期待します。
package my.package;
extend Foo {
repeated logs.proto.ValidationAnnotations event_annotations = 4;
}
package foo.package;
extend Foo {
optional int32 bar = 999;
}
予約済み宣言
拡張宣言は、もはや積極的に使用されておらず、拡張定義が削除されたことを示すために`reserved: true`とマークすることができます。拡張宣言を削除したり、その`type`や`full_name`の値を編集したりしないでください。
この`reserved`タグは、通常のフィールドの予約済みキーワードとは別であり、拡張範囲を分割する必要はありません。
edition = "2023";
message Foo {
extensions 4 to 1000 [
declaration = {
number: 500,
full_name: ".my.package.event_annotations",
type: ".logs.proto.ValidationAnnotations",
reserved: true }];
}
宣言で`reserved`とマークされた番号を使用する拡張フィールド定義は、コンパイルに失敗します。
descriptor.protoでの表現
拡張宣言は、descriptor.protoでは`proto2.ExtensionRangeOptions`のフィールドとして表現されます。
message ExtensionRangeOptions {
message Declaration {
optional int32 number = 1;
optional string full_name = 2;
optional string type = 3;
optional bool reserved = 5;
optional bool repeated = 6;
}
repeated Declaration declaration = 2;
}
リフレクションフィールドの検索
拡張宣言は、`Descriptor::FindFieldByName()`や`Descriptor::FindFieldByNumber()`のような通常のフィールド検索関数からは返されません。拡張機能と同様に、`DescriptorPool::FindExtensionByName()`のような拡張検索ルーチンによって発見可能です。これは、宣言が定義ではなく、完全な`FieldDescriptor`を返すのに十分な情報を持っていないという事実を反映した明示的な選択です。
宣言された拡張機能は、TextFormatやJSONの観点からは依然として通常の拡張機能のように動作します。これはまた、既存のフィールドを宣言された拡張機能に移行するには、そのフィールドのリフレクティブな使用を最初に移行する必要があることを意味します。
拡張宣言を使用して番号を割り当てる
拡張機能は通常のフィールドと同様にフィールド番号を使用するため、各拡張機能が親メッセージ内で一意の番号を割り当てられることが重要です。親メッセージ内の各拡張機能のフィールド番号と型を宣言するために拡張宣言を使用することをお勧めします。拡張宣言は親メッセージのすべての拡張機能のレジストリとして機能し、protocはフィールド番号の競合がないことを強制します。新しい拡張機能を追加するときは、通常、以前に追加された拡張機能の番号を1つ増やすことによって、次に利用可能な番号を選択します。
ヒント: `MessageSet`には、次に利用可能な番号を選択するのに役立つスクリプトを提供する特別なガイダンスがあります。
拡張機能を削除する際は、誤って再利用するリスクを排除するために、フィールド番号を必ず`reserved`としてマークしてください。
この慣習は単なる推奨事項であり、protobufチームは、すべての拡張可能なメッセージについて、これを遵守することを誰にも強制する能力も意志もありません。拡張可能なプロトの所有者であるあなたが、拡張宣言を通じて拡張番号を調整したくない場合は、他の手段で調整することを選択できます。ただし、拡張番号の偶発的な再利用は深刻な問題を引き起こす可能性があるため、非常に注意してください。
この問題を回避する方法の1つは、拡張機能を完全に避け、代わりに`google.protobuf.Any`を使用することです。これは、ストレージを前面に出すAPIや、クライアントがプロトの内容を気にしても、それを受け取るシステムが気にしないパススルーシステムに適した選択肢となるでしょう。
拡張番号を再利用することの帰結
拡張機能は、コンテナメッセージの外部で定義されるフィールドであり、通常は別の.protoファイルにあります。この定義の分散により、2人の開発者が誤って同じ拡張フィールド番号に対して異なる定義を作成してしまうことが容易になります。
拡張定義を変更することの影響は、拡張機能と標準フィールドで同じです。フィールド番号を再利用すると、ワイヤ形式からプロトをデコードする方法に曖昧さが生じます。Protobufのワイヤ形式は簡潔であり、ある定義を使用してエンコードされ、別の定義を使用してデコードされたフィールドを検出する良い方法を提供しません。
この曖昧さは、クライアントが一方の拡張定義を使用し、サーバーがもう一方の定義を使用して通信するなどの短期間で現れる可能性があります。
この曖昧さは、一方の拡張定義を使用してエンコードされたデータを保存し、後で2番目の拡張定義を使用して取得およびデコードするなどの長期間にわたって現れる可能性もあります。最初の拡張定義がデータがエンコードおよび保存された後に削除された場合、この長期間のケースは診断が困難になる可能性があります。
この結果は以下のようになります
- 解析エラー(最良のシナリオ)。
- PII/SPIIの漏洩 – PIIまたはSPIIが一方の拡張定義を使用して書き込まれ、別の拡張定義を使用して読み取られた場合。
- データ破損 – データが「誤った」定義を使用して読み取られ、変更されて書き直された場合。
データ定義の曖昧さは、少なくともデバッグに時間を要し、データ漏洩や破損を引き起こし、その修復に数ヶ月かかる可能性があります。
使用上のヒント
拡張宣言を絶対に削除しない
拡張宣言を削除すると、将来的に誤って再利用する道が開かれてしまいます。拡張機能が処理されなくなり、定義が削除された場合、拡張宣言は予約済みとしてマークできます。
新しい拡張宣言に`reserved`リストのフィールド名または番号を絶対に再利用しない
予約済みの番号は、過去にフィールドや他の拡張機能に使用されていた可能性があります。
Textprotoを使用する際に曖昧さが発生する可能性があるため、予約済みフィールドの`full_name`を使用することは推奨されません。
既存の拡張宣言の型を絶対にL変更しない
拡張フィールドの型を変更すると、データ破損につながる可能性があります。
拡張フィールドが列挙型またはメッセージ型であり、その列挙型またはメッセージ型が名前変更される場合、宣言名の更新は必須かつ安全です。破損を避けるために、型の更新、拡張フィールド定義、および拡張宣言はすべて1つのコミットで行う必要があります。
拡張フィールドの名前を変更する際は注意する
拡張フィールドの名前変更はワイヤ形式では問題ありませんが、JSONおよびTextFormatの解析を壊す可能性があります。