Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Dive into Go Protocol Buffers API v2 with the n...

Dive into Go Protocol Buffers API v2 with the new reflection features

About
2020年にリリースされたGo Protocol Buffers API v2のリリースされた背景や概要について解説しています。
特に、目玉機能のReflectionに焦点をあてて、実際の使用例とともに解説しています。
Go Conference Tokyo 2021 Springの登壇資料になります。

資料内のリンク
・使用例のコード
https://github.com/sryoya/protoreflect-go-examples

・The Go Blog A new Go API for Protocol Buffers https://blog.golang.org/protobuf-apiv2

・protobuf
https://github.com/protocolbuffers/protobuf

・protoreflect https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect

・dynamicpb
https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb

・protoc-gen-go
https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go

・protoc-gen-go-grpc
http:/pkg.go.dev/google.golang.org/grpc/cmd/protoc-gen-go-grpc

・protocmp
https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp

・protojson
https://pkg.go.dev/google.golang.org/protobuf/encoding/protojson

・protogen
https://pkg.go.dev/google.golang.org/protobuf/compiler/protogen

・採用ページ
https://corporatecard.up-sider.jp/career

Ryoya Sekino

April 24, 2021
Tweet

More Decks by Ryoya Sekino

Other Decks in Programming

Transcript

  1. Dive into Go Protocol Buffers API v2 With the new

    reflection features Ryoya Sekino @Go Conference 2021 Spring
  2. 2 セッションの背景とゴール 背景 ゴール • Go Protocol Buffers API v2が2020年にリリース

    • v1のリリースから10年ぶりのメジャーアップデート • 充実したreflection機能が公式に提供されるようになった • Go Protocol Buffers API v2の概要やリリース背景を理解する • 新しいreflection機能の使い方や使い道を理解する
  3. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 1. Faults in v1 2. What v2 provides 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 3
  4. $whoami • Ryoya Sekino /関野 涼也 • Software Engineer at

    UPSIDER, inc., leading Card Processing team • Writing Go for 2 years as a main language • Loves music, DJ’ing 4 1. Introduction sekino_pii sryoya
  5. 成長フェーズにつき、絶賛採用中です!! • 成長企業向けの法人カードを提供しているスタートアップです ⭐ • 金融SaaSとして企業のお金周りの課題を解決することを目指してい ます 🏢 • Tech

    Stacks: Go, Kotlin, k8s, Istio, microservices • Working Styles: フルリモート、フルフレックス、業務委託可、副業可 6 1. Introduction 問い合わせはこちらまで https://corporatecard.up-sider.jp/career Gopherの原著作者は Renée Frenchさんです。
  6. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 1. Faults in v1 2. What v2 provides 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 7
  7. 8 2.What’s Protocol Buffers? • IDL (インターフェース定義言語) で構造を定義し、データを Serialize/Deserializeする仕組み •

    高速でシンプルなのが特徴 • 言語や環境に依存しないで使用できる  メッセージが各言語の形にコンパイル するツールが提供されている • RPC(gRPC含む)で利用されている Protocol Buffers (a.k.a., protobuf, pb) は構造化データを Serializeする仕組み & そのインターフェース定義言語
  8. message Person { string name = 1; string phone_number =

    2; int32 age = 3; bool is_alive = 4; } このようなデータ形式のものを”.proto”という拡張子のファイルに定義する 9 2.What’s Protocol Buffers? 基本のデータ構造はmessageという単位で表現する 右端のタグ番号は、 Serializing用のフィールド識別子
  9. protobufには、各言語から操作できるようなAPIが用意されて いる • protoc コンパイラが各言語の表現形式に変換する https://github.com/protocolbuffers/protobuf • 対応言語はたくさん Go, C#,

    C++, Dart, Java, Kotlin, Node, Objective-C, PHP, Python, Ruby • Goでは、コンパイルすると、pb.goというファイルに変換される 10 2.What’s Protocol Buffers? .proto .pb.go コンパイル
  10. 11 2.What’s Protocol Buffers? Goにコンパイルするとmessageがstructとして表現される message Person { string name

    = 1; string phone_number = 2; int32 age = 3; bool is_alive = 4; } type Person struct { Name   string PhoneNumber string Age    int32 IsAlive bool } コンパイル .pb.go コンパイル .proto
  11. 13 2.What’s Protocol Buffers? このセッションでのProtocol Buffers関連の言葉の使い方 Protocol Buffers (protobuf, pb)

    : Serializingフォーマット、またはそのIDLのこと。 (protobufの) Message: protobufのIDLで定義されたメッセージ、またはそれの具 体的なデータのこと Proto File: protobufのIDLを定義したファイル(.proto)のこと Note: これらは必ずしも厳密な言葉の定義とは一致しません。
  12. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 1. Faults in v1 2. What v2 provides 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 14
  13. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 1. Faults in v1 2. What v2 provides 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 16
  14. • Goのreflect パッケージはあくまで、GoのTypeとしてしか扱えないので、 protobufのMessageをprotobufの文法に則って扱いたい • Messageの情報を取得する方法はあるが、簡単に操作するような方法が公式 で与えられていない 18 3-1. What’s

    Go Protocol Buffers API v2? - Faults in v1 v1の課題1/3: Messageに対して、protobuf Messageとして のreflectionが実行しづらい func (*DoResponse) Descriptor() ([]byte, []int) { // ... } Byteで返ってくる
  15. Messageのinterface自体は特になにもしてない。。。 19 3-1. What’s Go Protocol Buffers API v2? -

    Faults in v1 v1の課題2/3: Messageのinterfaceが簡素で、Messageに 対する共通の処理を実行しづらい 生成されたstructでも、特に何もしてない type Message interface { Reset() String() string ProtoMessage() } func (*DoRequest) ProtoMessage() {}
  16. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 1. Faults in v1 2. What v2 provides 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 21
  17. • Messageのinterfaceが、明示的に、reflection用のinterfaceを返すような関数 を宣言するようになった • protoreflect packageがprotobufに対して共通のreflectionを実装している 22 3-2. What’s Go

    Protocol Buffers API v2? - What v2 provides v2では、interfaceを充実させて、interfaceに対するreflection を提供することで、v1の課題を解決した
  18. package “google.golang.org/protobuf/proto” package “google.golang.org/protobuf/reflect/protoreflect” type ProtoMessage interface{ ProtoReflect() Message }

    23 3-2. What’s Go Protocol Buffers API v2? - What v2 provides v2のproto.Message interfaceは明示的にprotoreflectの interfaceを返す関数を宣言している リフレクション用のinterfaceをを返す type Message = protoreflect.ProtoMessage
  19. package “google.golang.org/protobuf/proto” package “google.golang.org/protobuf/reflect/protoreflect” func (x *DoRequest) ProtoReflect() protoreflect.Message {

    // … omitted ... } 24 3-2. What’s Go Protocol Buffers API v2? - What v2 provides protobufから生成されたmessage structから、reflection用 のinterfaceを実装したtypeを取得できる type ProtoMessage interface{ ProtoReflect() Message } type Message = protoreflect.ProtoMessage
  20. 25 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides protoreflect.MessageがReflection用のメソッドを定義してい る
  21. 27 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides protoファイルのデータ単位ごとにDescriptorが存在している • FileDescriptor • ServiceDescriptor • MethodDescriptor • MessageDescriptor • FieldDescriptor • OneOfDescriptor • EnumDescriptor • EnumValueDescriptor
  22. 28 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides Message DescriptorがMessageに対する操作を提供する
  23. 29 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides Field DescriptorがFieldに対する操作を提供する
  24. 30 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides FileDescriptorがファイル全体に対する操作を提供する
  25. 31 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides 同様にその他のデータ単位のDescriptorがある
  26. 32 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides データ単位同士の関係に則って、Descriptorを行ったり来たり できる File Descriptor Service Descriptor Method Descriptor Message Descriptor Field Descriptor Enum Descriptor OneOf Descriptor Enum Value Descriptor
  27. 33 3-2. What’s Go Protocol Buffers API v2? - What

    v2 provides GoのMessage Structがあれば、MessageDescriptorを始点 にすべてのDescriptorにアクセスできる File Descriptor Service Descriptor Method Descriptor Message Descriptor Field Descriptor Enum Descriptor OneOf Descriptor Enum Value Descriptor Message type Person struct { Name   string PhoneNumber string Age    int32 IsAlive bool } Proto Message
  28. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 34
  29. 1. Custom regulations for String protoreflectを使ったデータ構造や値の取り出し方の雰囲気 2. Random message struct

    generator protoreflectを使ったデータの作成・値の書き込み方法の雰囲気 最終形はGitHubに置いてます https://github.com/sryoya/protoreflect-go-examples 35 4. Examples using protoreflect Exampleを通して理解すること
  30. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 36
  31. 概要 • protobufのStringのフィールドに長さ・形式等の制約をかけられるようにしたい ◦ 文字数が6-12文字の値のみ有効なString ◦ アルファベットと数字のみ有効な String • protobufに制約を定義し、具体的なGoのメッセージの値が形式に沿っているか

    どうかのバリデーション関数を用意する 使い道の例 • gRPC Serverがいちいち(内部のロジック的に)想定外の値に対するバリデーショ ンを書かなくてよくする • gRPC Clientがサーバー側の内情を知らなくても、送ろうとしているメッセージが 正しいか判断できるようにする 37 4-1. Examples using protoreflect - Custom regulations for String 作るもの: Custom regulations for String
  32. 1. Stringのフィールドに長さのルールをOptionで付加する 2. Optionで指定した制約に準拠した値かをバリデーションできる 38 4-1. Examples using protoreflect -

    Custom regulations for String 使い方のイメージ: String with the custom regulations message UpdateAccountIDRequest { string iD = 1 [ (stroptpb.opts) = {min_len : 6, max_len: 12} ]; } msg = &testproto.UpdateAccountIDRequest{ ID: "12345", } err = stropt.Validate(msg) fmt.Println(err) // Output: Field: ID, invalid length, the value must be longer than or equal to 6, but actual: 5 Optionを指定
  33. 1. Protocol Buffersに共通のField Optionを定義する 2. 関数を作る 1. 関数のIOを定義する 2. Messageの情報を取り出す

    3. 各フィールドの情報を取り出す 4. フィールドのOptionをチェックして取り出す 5. フィールドの値をチェックして取り出す 6. 取り出したOptionと値を利用してバリデーションする 39 4-1. Examples using protoreflect - Custom regulations for String ステップ: Custom regulations for String
  34. Note: proto3には存在しなかった ”optional”は、Protocol Buffers v.3.12.0で試験的に導入され ました 40 4-1. Examples using

    protoreflect - Custom regulations for String 1. Protocol Buffersに共通のField Optionを定義する extend google.protobuf.FieldOptions { StringOpts opts = 50000; } message StringOpts { optional int32 len = 1; optional int32 max_len = 2; optional int32 min_len = 3; optional string regexp = 4; } message UpdateAccountIDRequest { string iD = 1 [ (stroptpb.opts) = {len : 10} ]; } 定義する 実際に埋め込める Optionの識別子は50000-99999でないと いけない
  35. Inputは、protobuf Messageから生成されたStructならどんな値でも投げ込めるよう にする 41 4-1. Examples using protoreflect - Custom

    regulations for String 2-1 関数のIOを定義する Messageから生成されたGoのStructに共通のinterface func Validate(pb proto.Message) error { return validate(pb.ProtoReflect()) }
  36. func Validate(pb proto.Message) error { return validate(pb.ProtoReflect()) } proto.Messageからreflection用のinterfaceを実装した値を取り出す 42

    4-1. Examples using protoreflect - Custom regulations for String 2-2 Messageの情報を取り出す reflection用のinterfaceを実装した情報を取り出す
  37. MessageからField Descriptorを取り出して各フィールドを見ていく func validate(m protoreflect.Message) error { md := m.Descriptor()

    fds := md.Fields() var errs error for k := 0; k < fds.Len(); k++ { fd := fds.Get(k) // validation for each filed } return errs } 43 4-1. Examples using protoreflect - Custom regulations for String 2-3 各フィールドの情報を取り出す この中に具体的なvalidationを書いていく MessageDescriptorを取り出す FieldDescriptorsを取り出す
  38. StrOptsがついているフィールドか確認して、ついていれば Optionの中身を取り出す opts := fd.Options().(*descriptorpb.FieldOptions) so, ok := proto.GetExtension(opts, stroptpb.E_Opts).(*stroptpb.StringOpts)

    if !ok || so == nil { continue } 44 4-1. Examples using protoreflect - Custom regulations for String 2-4: フィールドのOptionをチェックして取り出す Messageから生成されたStruct
  39. 1. Stringのフィールドか確認 if fd.Kind() != protoreflect.StringKind { continue } 2.

    protorefect.Messageから具体的な値を取得する (FieldDescriptorはあくまでデータ構造 を取得するものなので、具体的なフィールドの値は得られない ) strVal := m.Get(fd).Interface().(string) 45 4-1. Examples using protoreflect - Custom regulations for String 2-5: フィールドの値をチェックして取り出す Proto用の型リストと比較 protoreflect.Message
  40. func validateValue(fieldName protoreflect.Name, opts *stroptpb.StringOpts, v string) error { err

    = validateLength(opts, v) If err != nil { // error handling } // other validations... return errs } 46 4-1. Examples using protoreflect - Custom regulations for String 2-6: 取り出したOptionと値を利用してバリデーションする func validateLength(opts *stroptpb.StringOpts, v string) error { } 内容ごとのバリデーションの関数に、 Optionと値を渡す
  41. • Message: フィールドのtypeがまたMessageだったときに対応させる -> フィールドの型で判定し、再帰処理を実行する • List: StringのListに対応させる   -> 専用のメソッドで判定し、List用のinterfaceに変換して処理する

    • Map: ValueにStringを持つMapに対応させる -> 専用のメソッドで判定し、Map用のinterfaceに変換して処理する 48 4-1. Examples using protoreflect - Custom regulations for String 応用: Composite Typeに対応させる
  42. フィールドのTypeを見て再帰呼び出しするだけでOK 49 4-1. Examples using protoreflect - Custom regulations for

    String Message: フィールドのTypeが別のMessageだったときに対 応させる if fd.Kind() == protoreflect.MessageKind { errs = appendErr(errs, validate(m.Get(fd).Message())) continue }
  43. 専用のメソッドでListかチェックして、List用のinterfaceに変換した上でfor-loopを回 すだけ 50 4-1. Examples using protoreflect - Custom regulations

    for String List: StringのListに対応させる if fd.IsList() && fd.Kind() == protoreflect.StringKind { strList := m.Get(fd).List() for i := 0; i < strList.Len(); i++ { strVal := strList.Get(i).Interface().(string) errs = appendErr(errs, validateValue(fd.Name(), so, strVal)) } continue } Note: 通常(primitive type)のString用の条件分岐に入らないように注意 ListのTypeは、Listの中に入る値の Typeになる
  44. 基本的にListと同じだけど、Type判定とloopの回し型が違う 51 4-1. Examples using protoreflect - Custom regulations for

    String Map: ValueにStringを持つMapに対応させる if fd.IsMap() { strMap := m.Get(fd).Map() strMap.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool { strVal, ok := mv.Interface().(string) if !ok { // error handling return false } errs = appendErr(errs, validateValue(fd.Name(), so, strVal)) return true }) continue } Note: Map自体はMessage型になるので、Message型用の条件分岐に入らないように注意 ValueのTypeを確認しにいく Rangeでloopできる
  45. 52 4-1. Examples using protoreflect - Custom regulations for String

    Custom regulations for String - Summary このようにProtocol Buffersのデータ定義を読み込んだ処理ができるようになりました 最終形はGitHubにあります https://github.com/sryoya/protoreflect-go-examples/blob/master/stropt/valid ate.go
  46. Agenda 1. Introduction 2. What’s Protocol Buffers? 3. What’s Go

    Protocol Buffers API v2? 4. Examples using protoreflect 1. Custom regulations for String 2. Random message struct generator 53
  47. 54 4-2. Examples using protoreflect - Random message struct generator

    作るもの: Random message struct generator 概要 • Proto MessageのStructを受け取って、ランダムな値を埋め込んで返す • Proto MessageのStructならなんでも扱えるようにする 使い道の例 Fuzzing, Performance Test
  48. 基本: 元のMessageの値にアクセスして書き換える protoreflect.Message Interfaceは値を直接書き換える共通のメソッドを提供してい る 応用: 別のMessage Structを用意して値を設定し、最後に、元の Structマージする dynamicpbという、proto.Messageのinterfaceを実装した動的なtypeを生成できるパッ

    ケージがある。dynamicpbで生成したMessageは、同じproto.Messageのstructとマージ して値を書き込むことができる。 55 4-2. Examples using protoreflect - Random message struct generator 要点: protoreflectは、Message Structに値を直接書き込む 手段も提供している
  49. func EmbedValue(msg proto.Message) error { pm := msg.ProtoReflect() fds :=

    pm.Descriptor().Fields() for k := 0; k < fds.Len(); k++ { fd := fds.Get(k) // need handling for List and Map switch fd.Kind() { case protoreflect.StringKind: // write value   // other types including recursion default: return fmt.Errorf("unexpected type: %v", fd.Kind()) } } // ... } 56 4-2. Examples using protoreflect - Random message struct generator 読み込みは前の例と同じ要領
  50. protoreflect.Message interfaceを介して、MessageのStructに書き込める switch fd.Kind() { case protoreflect.Int32Kind: pm.Set(fd, protoreflect.ValueOfInt32(rand.Int31())) //

    ... default: return fmt.Errorf("unexpected type: %v", fd.Kind()) } 57 4-2. Examples using protoreflect - Random message struct generator 基本: 元のMessageの値にアクセスして値を書き換える Protoreflectの型定義に変換 Fieldに値をセットできる
  51. switch fd.Kind() { case protoreflect.MessageKind: // generate its child message

    pm.Set(fd, protoreflect.ValueofMessage(XXX))) default: return fmt.Errorf("unexpected type: %v", fd.Kind()) } 58 4-2. Examples using protoreflect - Random message struct generator ただし、この方法だと再帰処理などで元のMessageに辿るこ とが面倒な場合がある 親のMessageにセットするために、フィールドの Messageの値を用意しないといけない
  52. • Goのコンパイル時点で具体的なGoのStructが手に入らない状況でも、 Protocol Buffersのメッセージを作成できる ◦ ex) Protocol Buffersに対する汎用のCLI, カスタムのCompilerなど •

    再帰処理とかで元のMessageのStructを取り出して書き込むのが面倒なときに も、dynamicpbで具体的な値を作って、元のMessageにマージできる ◦ 今回のケース(ただし、dynamicpbを使わなくてもやる方法自体はある ) 60 4-2. Examples using protoreflect - Random message struct generator 応用: dynamicpbは、具体的なGoのMessage Structが手に 入らない状況でも、Messageの値が用意できる
  53. 1. MessageDescriptorを渡すと、Messageの情報を持ったdynamicなMessage Structが作成できる dm := dynamicpb.NewMessage(m.ProtoReflect().Descriptor()) 2. dynamicpbのMessageにも、Proto.Messageと同じように値をセットできる dm.Set(fd, value)

    61 4-2. Examples using protoreflect - Random message struct generator 応用: dynamicpbに、Messageの情報を渡して、値を生成さ せる 引数にいれる値は下記の通り 1. フィールドの情報(FieldDescriptor) 2. セットする値
  54. これで具体的な値が対象の Structにセットされる proto.Merge(msg, dm) 62 4-2. Examples using protoreflect -

    Random message struct generator 応用: dynamicpbで生成したStructをProcol Buffersから生 成されたMessage Structにマージする 引数にいれる値は下記の通り 1. セット先のMessage Struct 2. セット元のMessage Struct(dynamic pbが生成した値)
  55. 65 Appendix - Go Protocol Buffers API v2のimport path •

    google.golang.org/protobuf ◦ ちなみにv1はgithub.com/golang/protobuf • versionはv1.20.0から始まる ◦ まだ提供されているv1(2021年4月現在でv1.5.2)がv1.20まで到達するこ とはないだろうかららしい
  56. 66 Appendix - v2からのGoコード生成ツールと使用例 • protobufのGoコード生成 ◦ google.golang.org/protobuf/cmd/protoc-gen-go • gRPCのGoコード生成

    ◦ google.golang.org/grpc/cmd/protoc-gen-go-grpc • コマンド例 $ find . -name '*.proto' -exec protoc --experimental_allow_proto3_optional -I ./ --go_out . --go_opt paths=source_relative --go-grpc_out . --go-grpc_opt paths=source_relative {}
  57. 67 Appendix - その他関連パッケージ • protocmp ◦ google.golang.org/protobuf/testing/protocmp ◦ go-cmpを内包したテスト用のツール

    • protojson ◦ google.golang.org/protobuf/encoding/protojson ◦ Jsonとprotobufの変換(v1の改良版) • protogen ◦ google.golang.org/protobuf/compiler/protogen ◦ protobuf compilerの作成用のヘルパーツール
  58. 68 Reference • The Go Blog A new Go API

    for Protocol Buffers https://blog.golang.org/protobuf-apiv2 • protoreflect https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect • dynamicpb https://pkg.go.dev/google.golang.org/protobuf/types/dynamicpb