前書き
Googleの言語に依存しないデータ交換フォーマット であるプロトコルバッファ用のGoAPIのメジャーリビジョンのリリースを発表できることを嬉しく思います。
APIを更新するための前提条件
Goの最初のプロトコルバッファバインディングは、2010年3月にRobPikeによって導入されました。 Go 1は、今後2年間はリリースされません。
最初のリリースから10年間で、パッケージはGoとともに成長および開発されました。ユーザーの要望も高まっています。
多くの人は、リフレクションを使用してプロトコルバッファメッセージを処理するプログラムを作成したいと考えています。このパッケージで
reflectは、Goのタイプと値を表示できますが、プロトコルバッファータイプのシステム情報は省略されています。たとえば、ログ全体をスキャンし、機密データが含まれていると注釈が付けられたフィールドをクリアする関数を作成する必要がある場合があります。注釈はGoのタイプシステムの一部ではありません。
もう1つの一般的なニーズは、プロトコルバッファコンパイラによって生成されたもの以外のデータ構造を使用することです。たとえば、コンパイル時に不明なタイプのメッセージを表すことができる動的メッセージタイプなどです。
また、問題の一般的な原因はインターフェイスであることに気づきました
proto.Message、生成されたメッセージタイプの値を識別し、これらのタイプの動作の説明を省略します。ユーザーがこのインターフェイスを実装するタイプを作成し(多くの場合、メッセージを別の構造に埋め込むことによって不注意に)、生成されたメッセージ値を期待する関数にそれらのタイプの値を渡すと、プログラムがクラッシュしたり、予期しない動作をしたりします。
3つの問題はすべて同じルートと1つの解決策を持っています。インターフェイス
Messageはメッセージの動作を完全に定義するMessage必要があり、値を操作する関数はインターフェイスを正しく実装するすべてのタイプを自由に受け入れる必要があります。
パッケージのAPI互換性を維持したまま、メッセージタイプの既存の定義を変更することはできないため、protobufモジュールの互換性のない新しいメジャーリビジョンの作業を開始する時期であると判断しました。
本日、この新しいモジュールをリリースできることをうれしく思います。楽しんでいただければ幸いです。
反射
リフレクションは、新しい実装の主な機能です。パッケージ
reflectがGoのタイプと値のビューを提供するのと同様に、google.golang.org / protobuf / Reflect / protoreflectパッケージは、プロトコルバッファータイプシステムに従って値のビューを提供します。
完全なパッケージの説明
protoreflectはこの投稿には時間がかかりすぎますが、それでも、前述のログクリーンアップ関数を作成する方法を見てみましょう。
最初に
.proto、google.protobuf.FieldOptionsのような拡張子を定義するファイルを作成して、機密情報が含まれているかどうかにかかわらずフィールドに注釈を付ける必要があります。
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
bool non_sensitive = 50000;
}
このオプションを使用して、特定のフィールドを非機密としてマークできます。
message MyMessage {
string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}
次に、任意のメッセージ値を受け取り、すべての機密フィールドを削除するGo関数を作成する必要があります。
// Redact pb.
func Redact(pb proto.Message) {
// ...
}
この関数は
proto.Message 、生成されたすべてのメッセージタイプによって実装されるインターフェイスを受け入れます。このタイプは、パッケージで定義されているタイプのエイリアスですprotoreflect。
type ProtoMessage interface{
ProtoReflect() Message
}
生成されたメッセージネームスペースの入力を回避するために、インターフェイスには
protoreflect.Message、メッセージコンテンツへのアクセスを提供するreturnメソッドが1つだけ含まれています。
(なぜエイリアスなのですか?
protoreflect.Message元のを返す対応するメソッドがproto.Messageあり、2つのパッケージ間のインポートサイクルを回避する必要があるためです。)
このメソッド
protoreflect.Message.Rangeは、メッセージの入力フィールドごとに関数を呼び出します。
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
// ...
return true
})
範囲関数は
protoreflect.FieldDescriptor、フィールドのプロトコルバッファのタイプと、フィールドの値を含むprotoreflect.Valueを記述して呼び出されます。
このメソッド
protoreflect.FieldDescriptor.Optionsは、フィールドオプションをメッセージとして返しますgoogle.protobuf.FieldOptions。
opts := fd.Options().(*descriptorpb.FieldOptions)
(なぜタイプアサーション?生成されたパッケージ
descriptorpbはに依存するprotoreflectため、protoreflectパッケージはインポートサイクルを呼び出さずに特定のタイプのオプションを返すことはできません。)
次に、オプションをチェックして、拡張機能のブール変数の値を確認できます。
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true // non-sensitive
}
ここでは、フィールド値ではなく、フィールド記述子を確認していることに注意してください。関心のある情報は、Goではなくプロトコルバッファタイプのシステムからのものです。 これは、パッケージAPIを簡略化した領域の例でもあります。オリジナルは値とエラーの両方を返しました。newは値のみを返し、フィールドが欠落している場合はデフォルト値を返します。拡張デコードエラーはに報告されます。 編集が必要なフィールドを特定したら、それをクリアするのは簡単です。
protoproto.GetExtensionproto.GetExtensionUnmarshal
m.Clear(fd)
上記のすべてをまとめると、編集機能は次のようになります。
// Redact pb.
func Redact(pb proto.Message) {
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
opts := fd.Options().(*descriptorpb.FieldOptions)
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true
}
m.Clear(fd)
return true
})
}
より良いバージョンは、メッセージ値フィールドに再帰的に下降する可能性があります。この簡単な例が、プロトコルバッファでのリフレクションとその使用法の概要を提供することを願っています。
バージョン
元のバージョンのプロトコルバッファをGoAPIv1と呼び、新しいバージョンをAPIv2と呼びます。 APIv2はAPIv1と下位互換性がないため、それぞれに異なるモジュールパスを使用する必要があります。
(APIのこのバージョンは、言語バッファプロトコルのバージョンと同じではない:
proto1、proto2、およびproto3。APIv1とAPIv2 -進み、サポート言語バージョンの両方において、この特定の実装proto2とproto3)
モジュールでgithub.com/golang/protobuf - APIv1。google.golang.org/protobuf
モジュール内-APIv2。インポートパスを変更して、特定のホスティングプロバイダーに関連付けられていないパスに切り替える必要があることを利用しました。 (私たちは考えました
google.golang.org/protobuf/v2これがAPIの2番目のメジャーバージョンであることを明確にするためですが、長期的にはより短いパスを選択
することをお勧めします。)すべてのユーザーが同じ速度でパッケージの新しいメジャーバージョンに移行するわけではないことを理解しています。すぐに切り替わるものもあります。他の人は無期限に古いバージョンに残るかもしれません。同じプログラム内であっても、一部の部分は1つのAPIを使用し、他の部分は別のAPIを使用する場合があります。したがって、APIv1を使用するプログラムを引き続きサポートすることが重要です。
github.com/golang/protobuf@v1.3.4APIv2より前のAPIv1の最新バージョンです。github.com/golang/protobuf@v1.4.0APIv2に基づいて実装されたAPIv1のバージョンです。APIは同じですが、基本的な実装は新しいAPIでサポートされています。このバージョンには、proto.MessageAPIv1とAPIv2の間の移行を容易にするために、それらの間で変換するための関数が含まれています。google.golang.org/protobuf@v1.20.0— APIv2.github.com/golang/protobuf@v1.4.0, , APIv2, APIv1, .
(バージョンから始めたのはなぜ
v1.20.0ですか?わかりやすくするv1.20.0ために、APIv1が到達するとは思わないため、APIv1とAPIv2を一意に区別するには単一のバージョン番号で十分です。)
期限を設定せずにAPIv1を引き続きサポートする予定です。
この配置により、使用するAPIのバージョンに関係なく、すべてのプログラムが1つのプロトコルバッファー実装のみを使用することが保証されます。これにより、プログラムは、新しい実装の利点を維持しながら、新しいAPIを段階的に実装することも、まったく実装しないこともできます。最小バージョンを選択するという原則は、メンテナがそれを新しいバージョンに更新することを決定するまで(直接または依存関係を更新することによって)、プログラムが古い実装のままでいられることを意味します。
注意すべき追加機能
このパッケージは、正規のJSONマッピング
google.golang.org/protobuf/encoding/protojsonを使用してプロトコルバッファメッセージをJSONとの間で変換します。また、既存のユーザーに新しい問題を引き起こすことなく変更することが困難であった、古いパッケージに関する多くの問題を修正します。
このパッケージは、プロトコルバッファタイプが実行時に決定されるメッセージの実装を提供します。
このパッケージは、メッセージのプロトコルバッファをパケットと比較するための関数を提供します。
このパッケージは、プロトコルバッファコンパイラプラグインの作成をサポートします。jsonpb
google.golang.org/protobuf/types/dynamicpbproto.Message
google.golang.org/protobuf/testing/protocmpgithub.com/google/cmp
google.golang.org/protobuf/compiler/protogen
結論
このモジュール
google.golang.org/protobufは、プロトコルバッファのGoサポートの大幅な見直しであり、リフレクション、カスタムメッセージング、およびクリーンアップされたAPIのファーストクラスのサポートを提供します。以前のAPIを新しいAPIのラッパーとして引き続きサポートし、ユーザーが自分のペースで新しいAPIを徐々に実装できるようにする予定です。
このアップデートの目標は、古いAPIの利点を強化し、その弱点に対処することです。新しい実装の各コンポーネントが完了すると、Googleコードベースでの使用を開始しました。この段階的な展開により、新しいAPIの使いやすさと、新しい実装のパフォーマンスと正確性の両方に自信が持てるようになりました。生産の準備ができていると確信しています。
このリリースに非常に興奮しており、今後10年以上にわたってGoエコシステムに役立つことを願っています。
コースの詳細をご覧ください。