因為工作上需要使用 Protocol Buffers 當作資料交換的結構,那麼就來看看 Protocol Buffers 有什麼過人之處。
以下說明所使用的環境
OS :Mac
Golang:1.14
Editer:Visual Studio Code
Protocol Buffers
Protocol Buffers 是一種序列化資料結構,由 Google 設計維護,目前有二個版本 proto2 和 proto3,主打比其它資料結構更小、更容易使用。
Insatll
- 安裝 protocol compiler:
1
2
|
brew install protobuf
brew --version
|
- 安裝 protobuf runtime:
因為我是使用 golang,所以安裝 golang 的 protobuf runtime。
1
2
|
go get -u -v github.com/golang/protobuf/proto
go get -u -v github.com/golang/protobuf/protoc-gen-go
|
設定環境變數
1
|
sudo cp $(gopath)/bin/protoc-gen-go /usr/local/bin/
|
.proto
安裝完 Protocol Buffers 後,接著建立一個 user.proto,Protocol Buffers 定義檔的副檔名為 .proto。
1
2
3
|
mkdir goprotobuf/model
cd goprotobuf/model
touch user.proto
|
user.proto
1
2
3
4
5
6
7
8
9
10
11
12
|
syntax = "proto3";
package protomvc;
option go_package = ".;model";
message user {
int64 id = 1;
string name = 2;
string email = 3;
string addr = 4;
string phone = 5;
}
|
可以看到 user.proto 的內容是有逹到 Google 所宣稱的容易簡單,其它 message type 可參考available wire types。
.pb.go
當定義好 Protocol Buffers 資料傳輸結構後,接著將 user.proto 轉成 go struct,加入我們的 go package,透過 protocol compiler 來產生 user.pb.go。
1
|
protoc --go_out=. *.proto
|
user.pb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// ~~~~ 略
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
Addr string `protobuf:"bytes,4,opt,name=addr,proto3" json:"addr,omitempty"`
Phone string `protobuf:"bytes,5,opt,name=phone,proto3" json:"phone,omitempty"`
}
// ~~~~略
|
user.pb.go 的內容 是由 protocol compiler 所自動生成,請不要直接修改。
Marshal/Unmarshal
接下來建立一個 user 資料,然後產生 Protocol Buffers 序列資料,範例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func main() {
user := &model.User{
Id: 1,
Name: "john",
Email: "john@gmail.com",
Addr: "taipei",
Phone: "0212348765",
}
buf, err := proto.Marshal(user)
if err != nil {
fmt.Println(err)
}
fmt.Println(buf)
newUser := &model.User{}
err = proto.Unmarshal(buf, newUser)
if err != nil {
fmt.Println(err)
}
fmt.Println(newUser)
}
|
這裡特別需注意 grpc,protobuf 和 protoc-gen-go 版本是否相容。
假設當中某一個的版本較高,可能會有找不到方法的問題 proto.Marshal: missing method ProtoReflect。
Polymorphism
接著說說 Protocol Buffers 的進階用法,例如你回應內容可能會有多種類型,Protocol Buffers 也提供了 any 的類型,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
syntax = "proto3";
package model;
import "google/protobuf/any.proto";
option go_package = ".;model";
message body1 {
string name = 1;
}
message body2 {
int age = 1;
}
message Response {
google.protobuf.Any message = 2;
}
|
Protocol Buffers any 在 golang 的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import (
"github.com/golang/protobuf/ptypes"
"google.golang.org/protobuf/proto"
)
func main() {
body := &model.body1{
Name: "redd1",
}
m, err := ptypes.MarshalAny(body)
if err != nil {
fmt.Println(err)
}
fmt.Println(m.TypeUrl)
}
|
UnmarshalAny 需先判斷資料的 TypeUrl,在使用相對定義結構來反序列。
Cap
最後看看 Google 宣稱的容量比較小,以下的範列 Protocol Buffers 容量 44,JSON 容量 96。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func main() {
user := &model.User{
Id: 1,
Name: "john",
Email: "john@gmail.com",
Addr: "taipei",
Phone: "0212348765",
}
bufProto, err := proto.Marshal(user)
if err != nil {
fmt.Println(err)
}
bufJson, err := json.Marshal(user)
if err != nil {
fmt.Println(err)
}
fmt.Println(cap(bufProto))
fmt.Println(cap(bufJson))
}
|
Reference
Protocol Buffers
golang protobuf