Go Opaque API: 手動移行
Opaque API は、Go プログラミング言語向けの Protocol Buffers 実装の最新バージョンです。古いバージョンは現在 Open Struct API と呼ばれています。詳細は、Go Protobuf: Releasing the Opaque API のブログ記事をご覧ください。
これは、Go Protobuf の使用方法を古い Open Struct API から新しい Opaque API に移行するためのユーザーガイドです。
生成コードガイドで詳細を説明しています。このガイドでは、古い API と新しい API を並べて比較しています。
メッセージの構築
次のように定義された protobuf メッセージがあるとします
message Foo {
uint32 uint32 = 1;
bytes bytes = 2;
oneof union {
string string = 4;
MyMessage message = 5;
}
enum Kind { … };
Kind kind = 9;
}
リテラル値からこのメッセージを構築する方法の例を次に示します
| Open Struct API (旧) | Opaque API (新) |
| |
ご覧のとおり、ビルダー構造体を使用すると、Open Struct API (旧) と Opaque API (新) の間でほぼ 1:1 の変換が可能です。
一般に、可読性のためにビルダーを使用することを推奨します。ホットな内部ループで Protobuf メッセージを作成するような稀なケースでのみ、ビルダーの代わりにセッターを使用する方が望ましい場合があります。詳細については、Opaque API FAQ: ビルダーとセッターのどちらを使用すべきか? を参照してください。
上記の例の例外は、oneof を扱う場合です。Open Struct API (旧) は各 oneof ケースにラッパー構造体型を使用しますが、Opaque API (新) は oneof フィールドを通常のメッセージフィールドのように扱います
| Open Struct API (旧) | Opaque API (新) |
| |
oneof ユニオンに関連付けられた Go 構造体フィールドのセットでは、1 つのフィールドのみを格納できます。複数の oneof ケースフィールドが格納された場合、最後のもの ( .proto ファイルでのフィールド宣言順) が優先されます。
スカラフィールド
スカラフィールドで定義されたメッセージがあるとします
message Artist {
int32 birth_year = 1;
}
Go がスカラ型 (bool, int32, int64, uint32, uint64, float32, float64, string, []byte, および enum) を使用する Protobuf メッセージフィールドには、Get および Set アクセサメソッドがあります。明示的な存在を持つフィールドには、Has および Clear メソッドも含まれます。
birth_year という名前の int32 型のフィールドの場合、次のアクセサメソッドが生成されます
func (m *Artist) GetBirthYear() int32
func (m *Artist) SetBirthYear(v int32)
func (m *Artist) HasBirthYear() bool
func (m *Artist) ClearBirthYear()
Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、デフォルト値を返します。デフォルト値は、デフォルトオプションで明示的に設定されていない限り、ゼロ値です。
Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。
バイトフィールドの場合、nil の []byte で Set を呼び出すと、設定されていると見なされます。たとえば、直後に Has を呼び出すと true を返します。直後に Get を呼び出すと、長さゼロのスライス (nil または空のスライスのいずれか) が返されます。ユーザーは、存在を判断するために Has を使用し、Get が nil を返すかどうかに依存すべきではありません。
Has はフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。
Clear はフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。
文字列フィールドを使用したコードスニペットの例
| Open Struct API (旧) | Opaque API (新) |
| |
メッセージフィールド
メッセージ型のフィールドで定義されたメッセージがあるとします
message Band {}
message Concert {
Band headliner = 1;
}
メッセージ型の Protobuf メッセージフィールドには、Get、Set、Has、および Clear メソッドがあります。
headliner という名前のメッセージ型フィールドの場合、次のアクセサメソッドが生成されます
func (m *Concert) GetHeadliner() *Band
func (m *Concert) SetHeadliner(*Band)
func (m *Concert) HasHeadliner() bool
func (m *Concert) ClearHeadliner()
Get はフィールドの値を返します。設定されていない場合、または nil メッセージレシーバーで呼び出された場合は nil を返します。Get が nil を返すかどうかをチェックすることは、Has が false を返すかどうかをチェックすることと同じです。
Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。nil ポインタで Set を呼び出すことは、Clear を呼び出すことと同じです。
Has はフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。
Clear はフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。
コードスニペットの例
| Open Struct API (旧) | Opaque (新) |
| |
繰り返しフィールド
繰り返しメッセージ型フィールドで定義されたメッセージがあるとします
message Concert {
repeated Band support_acts = 2;
}
繰り返しフィールドには、Get および Set メソッドがあります。
Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、nil を返します。
Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。Set は、提供されたスライスヘッダーのコピーを格納します。スライスコンテンツへの変更は、繰り返しフィールドで観測可能です。したがって、空のスライスで Set が呼び出されると、直後に Get を呼び出すと、同じスライスが返されます。ワイヤーまたはテキストマーシャリング出力の場合、渡された nil スライスは空のスライスと区別できません。
Concert メッセージの support_acts という名前の繰り返しメッセージ型フィールドの場合、次のアクセサメソッドが生成されます
func (m *Concert) GetSupportActs() []*Band
func (m *Concert) SetSupportActs([]*Band)
コードスニペットの例
| Open Struct API (旧) | Opaque API (新) |
| |
マップ
マップ型フィールドで定義されたメッセージがあるとします
message MerchBooth {
map<string, MerchItems> items = 1;
}
マップフィールドには、Get および Set メソッドがあります。
Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、nil を返します。
Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。Set は、提供されたマップ参照のコピーを格納します。提供されたマップへの変更は、マップフィールドで観測可能です。
MerchBooth メッセージの items という名前のマップフィールドの場合、次のアクセサメソッドが生成されます
func (m *MerchBooth) GetItems() map[string]*MerchItem
func (m *MerchBooth) SetItems(map[string]*MerchItem)
コードスニペットの例
| Open Struct API (旧) | Opaque API (新) |
| |
Oneof
oneof ユニオンのグループごとに、メッセージに Which、Has、および Clear メソッドがあります。また、そのユニオン内の各 oneof ケースフィールドには、Get、Set、Has、および Clear メソッドがあります。
oneof avatar 内に image_url と image_data の oneof フィールドを持つメッセージが次のように定義されているとします
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
この oneof に対して生成される Opaque API は次のようになります
func (m *Profile) WhichAvatar() case_Profile_Avatar { … }
func (m *Profile) HasAvatar() bool { … }
func (m *Profile) ClearAvatar() { … }
type case_Profile_Avatar protoreflect.FieldNumber
const (
Profile_Avatar_not_set_case case_Profile_Avatar = 0
Profile_ImageUrl_case case_Profile_Avatar = 1
Profile_ImageData_case case_Profile_Avatar = 2
)
Which は、フィールド番号を返すことで、どのケースフィールドが設定されているかを報告します。何も設定されていない場合、または nil メッセージレシーバーで呼び出された場合は 0 を返します。
Has は、oneof 内のいずれかのフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。
Clear は、oneof 内の現在設定されているケースフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。
各 oneof ケースフィールドに対して生成される Opaque API は次のようになります
func (m *Profile) GetImageUrl() string { … }
func (m *Profile) GetImageData() []byte { … }
func (m *Profile) SetImageUrl(v string) { … }
func (m *Profile) SetImageData(v []byte) { … }
func (m *Profile) HasImageUrl() bool { … }
func (m *Profile) HasImageData() bool { … }
func (m *Profile) ClearImageUrl() { … }
func (m *Profile) ClearImageData() { … }
Get はケースフィールドの値を返します。ケースフィールドが設定されていない場合、または nil メッセージレシーバーで呼び出された場合は、ゼロ値を返します。
Set は指定された値をケースフィールドに格納します。また、oneof ユニオン内で以前に設定されていたケースフィールドも暗黙的にクリアします。oneof メッセージケースフィールドを nil 値で Set を呼び出すと、フィールドは空のメッセージに設定されます。nil メッセージレシーバーで呼び出されるとパニックになります。
Has はケースフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。
Clear はケースフィールドをクリアします。以前に設定されていた場合、oneof ユニオンもクリアされます。oneof ユニオンが異なるフィールドに設定されている場合、oneof ユニオンはクリアされません。nil メッセージレシーバーで呼び出されるとパニックになります。
コードスニペットの例
| Open Struct API (旧) | Opaque API (新) |
| |
リフレクション
Open Struct API から移行すると、Go reflect パッケージを使用して構造体フィールドとタグにアクセスする proto メッセージ型を使用するコードは機能しなくなります。コードは protoreflect を使用するように移行する必要があります
一部の一般的なライブラリは、内部で Go reflect を使用しています。例を次に示します。
- encoding/json
- protobuf/encoding/protojson を使用します。
- pretty
- cmp
- protobuf メッセージで
cmp.Equalを適切に使用するには、protocmp.Transform を使用します
- protobuf メッセージで