Go Opaque API: 手動移行
Opaque API は、Go プログラミング言語向けの Protocol Buffers 実装の最新バージョンです。古いバージョンは Open Struct API と呼ばれています。詳細は、Go Protobuf: Opaque API のリリースというブログ記事をご覧ください。
これは、Go Protobuf の使用方法を古い Open Struct API から新しい Opaque API へ移行するためのユーザーガイドです。
警告
これは手動移行ガイドです。通常、移行を自動化するには `open2opaque` ツールを使用することをお勧めします。代わりに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 メッセージレシーバで呼び出された場合、パニックになります。
bytes フィールドの場合、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 (新) |
|
|
Oneofs
各 oneof 共用体グループには、メッセージに `Which`、`Has`、および `Clear` メソッドがあります。また、その共用体内の各 oneof ケースフィールドには、`Get`、`Set`、`Has`、および `Clear` メソッドがあります。
`avatar` oneof に `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 共用体内で以前に格納されていたケースフィールドも暗黙的にクリアします。nil 値を持つ oneof メッセージケースフィールドで `Set` を呼び出すと、フィールドが空のメッセージに設定されます。nil メッセージレシーバで呼び出された場合、パニックになります。
`Has` はケースフィールドが設定されているかどうかを報告します。nil メッセージレシーバで呼び出された場合、false を返します。
`Clear` はケースフィールドをクリアします。以前に設定されていた場合、oneof 共用体もクリアされます。oneof 共用体が異なるフィールドに設定されている場合、oneof 共用体はクリアされません。nil メッセージレシーバで呼び出された場合、パニックになります。
コードスニペットの例
Open Struct API (旧) | Opaque API (新) |
|
|
リフレクション
Go の `reflect` パッケージを使用して proto メッセージ型の構造体フィールドやタグにアクセスするコードは、Open Struct API から移行すると動作しなくなります。コードはprotoreflectを使用するように移行する必要があります。
いくつかの一般的なライブラリは、内部で Go の `reflect` を使用しています。例としては、
- encoding/json
- protobuf/encoding/protojson を使用してください。
- pretty
- cmp
- protobuf メッセージで `cmp.Equal` を適切に使用するには、protocmp.Transform を使用してください。