Please enable Javascript to view the contents

使用 Protocol Buffers 結構化資料 In Go

· ☕ 2 分钟 · 🐔 Redd Tsai
🏷️
  • #Go
  • 因為工作上需要使用 Protocol Buffers 當作資料交換的結構,那麼就來看看 Protocol Buffers 有什麼過人之處。

    Protocol Buffers

    Protocol Buffers 是一種序列化資料結構,由 Google 設計維護,目前有二個版本 proto2 和 proto3,主打比其它資料結構更小、更容易使用。

    Insatll

    1. 安裝 protocol compiler:
      1
      2
      
      brew install protobuf
      brew --version
      
    2. 安裝 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"`
    }
    
    // ~~~~略
    

    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)
    }
    

    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

    分享

    蔡文杰
    作者
    Redd Tsai
    Backend Developer