Remote Procedure Call で使われてきたデータフォーマットが、XMLのXML-RPC、JSONのJSON-RPCと使われてきましたが、どちらもHTTPのテキストデータであることから、バイナリデータのサイズが大きくなったり、データ変換の効率が悪かったりしました。そこでgRPCというHTTP/2のストリーム多重化通信の上に、バイナリデータを効率よく送ることができるフォーマットProtocol Buffersを最近よく見かけるようになりました。(gRPCのロードバランシングとか)
そこで、構造データのシリアライズ(marshalling)、デシリアライズ(unmarshalling)をしている部分を実際にためしてみました。(バイナリデータを確認)
「HTTP/2 packet」
https://decode.red/net/archives/79
Protocol Buffersの優れているところは開発言語を選ばないことです。.protoというファイルにデータ構造を定義すると、そのファイルからさまざまな言語のインターフェイスとなるコードを生成してくれます。これを利用してデータ変換をします。
ここではGo言語でテストしました。
またKubernetesの管理KVSであるetcdのValue値にもProtocol Buffersのエンコードが使われているようです。
「kubernetesのetcd上のデータを読み解く」
https://qiita.com/kuro_1116/items/358e82547e25c1c60820
あらゆるデータフォーマットの共通化は、この世界の過去の事例を見ても難しいことですが、データにアクセスする仕組み共通化するというアイディアによって、現実的になった感があります。
環境)
Mac Ventura / M2
参考)
https://qiita.com/nozmiz/items/fdbd052c19dad28ab067
コンパイラのダウンロード
https://github.com/protocolbuffers/protobuf/releases/
https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-osx-aarch_64.zip
内容
% ls bin
protoc
% ls include/google/protobuf
any.proto duration.proto struct.proto
api.proto empty.proto timestamp.proto
compiler field_mask.proto type.proto
descriptor.proto source_context.proto wrappers.proto
protoc は、$GOPATH/bin に、include/googleフォルダは、コンパイルする.protoファイルと同じ場所に配置。
プラグインインストール
go get google.golang.org/protobuf/cmd/protoc-gen-go
確認
% protoc –version
libprotoc 23.4
% protoc-gen-go –version
protoc-gen-go v1.31.0
.proto ファイル取得
wget https://raw.githubusercontent.com/protocolbuffers/protobuf/main/examples/addressbook.proto
コンパイル
protoc addressbook.proto –go_out=./
下記に出力
github.com/protocolbuffers/protobuf/examples/go/tutorialpb/addressbook.pb.go
テストプログラム
https://github.com/protocolbuffers/protobuf/tree/main/examples/go/cmd
add_person でユーザ情報をコンソールからインタラクティブに登録してファイルに書き出し、list_peopleでファイルを読み込んで表示します。
addressbook.pb.go がローカルでインポートできなかったため、(tutorialpbがgithubにないためか・・相対パスは禁止) 禁じ手ですが、/opt/homebrew/Cellar/go/1.20.5/libexec/src/example におきました。(要ソース変更)
—-
.protoファイルから Go言語ソースを生成し、それを呼び出してアプリケーションadd_person.go、list_people.goを作っています。
import変更部分
//pb “github.com/protocolbuffers/protobuf/examples/go/tutorialpb”
pb “example”
バイナリデータを確認できたことで、理解がふかまりました。
アメリカの物理学者リチャード・フィリップス・ファインマンの名言に「自分に作れないものは、理解できない」というものがあります。最近新しい概念やキーワードが飛び交うクラウド関連の学習をしていて特に響く言葉です。
今回のテストでもいろいろとドキュメントはあるのですが、実際に最後までやったものがなかったため、試すことにしました。こういうことはこれまでもよくあります。
まだまだ、調べたいことばかりですが、一つずつ消化していきたいと思います。
addressbook.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// See README.md for information and build instructions. // // Note: START and END tags are used in comments to define sections used in // tutorials. They are not part of the syntax for Protocol Buffers. // // To get an in-depth walkthrough of this file and the related examples, see: // https://developers.google.com/protocol-buffers/docs/tutorials // [START declaration] syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto"; // [END declaration] // [START java_declaration] option java_multiple_files = true; option java_package = "com.example.tutorial.protos"; option java_outer_classname = "AddressBookProtos"; // [END java_declaration] // [START csharp_declaration] option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; // [END csharp_declaration] // [START go_declaration] option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb"; // [END go_declaration] // [START messages] message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; } // Our address book file is just one of these. message AddressBook { repeated Person people = 1; } // [END messages] |
addressbook.pb.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// See README.md for information and build instructions. // // Note: START and END tags are used in comments to define sections used in // tutorials. They are not part of the syntax for Protocol Buffers. // // To get an in-depth walkthrough of this file and the related examples, see: // https://developers.google.com/protocol-buffers/docs/tutorials // [START declaration] // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc v4.23.4 // source: addressbook.proto package tutorialpb import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type Person_PhoneType int32 const ( Person_MOBILE Person_PhoneType = 0 Person_HOME Person_PhoneType = 1 Person_WORK Person_PhoneType = 2 ) // Enum value maps for Person_PhoneType. var ( Person_PhoneType_name = map[int32]string{ 0: "MOBILE", 1: "HOME", 2: "WORK", } Person_PhoneType_value = map[string]int32{ "MOBILE": 0, "HOME": 1, "WORK": 2, } ) (以下省略) |
add_person.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
(上部省略) // Main reads the entire address book from a file, adds one person based on // user input, then writes it back out to the same file. func main() { if len(os.Args) != 2 { log.Fatalf("Usage: %s ADDRESS_BOOK_FILE\n", os.Args[0]) } fname := os.Args[1] // Read the existing address book. in, err := ioutil.ReadFile(fname) if err != nil { if os.IsNotExist(err) { fmt.Printf("%s: File not found. Creating new file.\n", fname) } else { log.Fatalln("Error reading file:", err) } } // [START marshal_proto] book := &pb.AddressBook{} // [START_EXCLUDE] if err := proto.Unmarshal(in, book); err != nil { log.Fatalln("Failed to parse address book:", err) } // Add an address. addr, err := promptForAddress(os.Stdin) if err != nil { log.Fatalln("Error with address:", err) } book.People = append(book.People, addr) // [END_EXCLUDE] // Write the new address book back to disk. out, err := proto.Marshal(book) if err != nil { log.Fatalln("Failed to encode address book:", err) } if err := ioutil.WriteFile(fname, out, 0644); err != nil { log.Fatalln("Failed to write address book:", err) } // [END marshal_proto] } |
list_people.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
(上部省略) // Main reads the entire address book from a file and prints all the // information inside. func main() { if len(os.Args) != 2 { log.Fatalf("Usage: %s ADDRESS_BOOK_FILE\n", os.Args[0]) } fname := os.Args[1] // [START unmarshal_proto] // Read the existing address book. in, err := ioutil.ReadFile(fname) if err != nil { log.Fatalln("Error reading file:", err) } book := &pb.AddressBook{} if err := proto.Unmarshal(in, book); err != nil { log.Fatalln("Failed to parse address book:", err) } // [END unmarshal_proto] listPeople(os.Stdout, book) } |