いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

Goメモ-108 (Protocol Buffers でシリアライズとデシリアライズ)

概要

少し前に grpc のメモを書いたのですが

devlights.hatenablog.com

ついでなので、Protocol Buffers 単体で使用してシリアライズとデシリアライズのメモです。

Protocol Buffers (protobuf) のページは以下。

developers.google.com

サンプルについて

今回もサンプルはついでなので github にアップしてあります。良かったらご参照ください。

github.com

Protocol Buffers を動作させる上で必要なものをインストール

Protocol Buffers を Go で動作させるには

  • protocol buffers の コンパイラ protoc
  • Go のソースを吐き出すジェネレータ

が必要になります。インストールについては上記の grpc のブログ記事で書いていますので参照ください。

で、上の記事を見ながらインストールしてもいいのですが、Protocol Buffersでサンプル書くたびに打つの面倒なので

gistに makefile アップしときました。

Protocol Buffers (protobuf) と Go のプログラム作るときに使える M…

この makefile 落として

make install-requirements

ってやるとprotocとかprotoc-gen-goのライブラリを落としてきます。

ご利用される場合は、makefile内の protoc のURLとかprotocの生成物の置き場所とかを適時ご自身の環境に調整ください。

私は Crostini (Chromebook の Linux) で作業していますので、ARM64版 (aarch64) のprotoc を使っています。

protoファイルを作成

セットアップが終わったら、次は protoファイルの作成です。

以下のようにしました。

syntax = "proto3";

package main;
option go_package = "internal/pb";

message person {
    string name = 1;
    int32 age = 2;
}

Go で使う場合は option go_package="..." を付けて、Goのパッケージを指定します。

protoファイルからGoのコードを生成

次に protoファイル からGoのコードを生成します。

上で載せている makefile には、すでにタスクを定義してあります。

$ make protoc 
mkdir -p internal
bin/protoc/bin/protoc -I=proto --go_out=. proto/*.proto
mkdir -p doc/proto
bin/protoc/bin/protoc --doc_out=html,index.html:./doc/proto proto/*.proto

で生成されます。

生成されたファイルはこんな感じになります。

// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
//     protoc-gen-go v1.25.0
//     protoc        v3.13.0
// source: person.proto

package pb

import (
    proto "github.com/golang/protobuf/proto"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
    protoimpl "google.golang.org/protobuf/runtime/protoimpl"
    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)
)

// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4

type Person struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}

func (x *Person) Reset() {
    *x = Person{}
    if protoimpl.UnsafeEnabled {
        mi := &file_person_proto_msgTypes[0]
        ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
        ms.StoreMessageInfo(mi)
    }
}

func (x *Person) String() string {
    return protoimpl.X.MessageStringOf(x)
}

・・・長いので割愛・・・

サンプル

てことで、シリアライズとデシリアライズのサンプルです。

Protocol Buffers使っている場合は、シリアライズに proto.Marshal() 、デシリアライズに proto.Unmarshal() を使います。

めっちゃ簡単ですね。

package main

import (
    "fmt"
    "log"
    "protobuftest/internal/pb"

    "google.golang.org/protobuf/proto"
)

func main() {
    // ---------------------------------------------
    // Protocol Buffers を使ったシリアライズとデシリアライズのサンプルです.
    //
    // REFERENCES:
    //   - https://developers.google.com/protocol-buffers/docs/gotutorial
    //   - https://dev.to/heerthees/protobuf-with-go-4hb
    // ---------------------------------------------

    // ---------------------------------------------
    // シリアライズ
    // ---------------------------------------------
    p := &pb.Person{
        Name: "hoge",
        Age:  99,
    }

    data, err := proto.Marshal(p)
    if err != nil {
        log.Fatal(err)
    }

    // - https://devlights.hatenablog.com/entry/2020/08/18/014703
    fmt.Printf("[marshal  ] [%10T] %[1]v\n", data)

    // ---------------------------------------------
    // デシリアライズ
    // ---------------------------------------------
    p2 := new(pb.Person)

    err = proto.Unmarshal(data, p2)
    if err != nil {
        log.Fatal(err)
    }

    // - https://devlights.hatenablog.com/entry/2020/08/18/014703
    fmt.Printf("[unmarshal] [%10T] %[1]v\n", p2)
}

実行すると以下のようになります。

$ make run
go run cmd/example/main.go
[marshal  ] [   []uint8] [10 4 104 111 103 101 16 99]
[unmarshal] [*pb.Person] name:"hoge"  age:99

ちゃんとバイナリ化したものが復元できていますね。

参考情報

developers.google.com

dev.to

おすすめ書籍

自分が読んだGo関連の本で、いい本って感じたものです。

Go言語による並行処理

Go言語による並行処理

スターティングGo言語 (CodeZine BOOKS)

スターティングGo言語 (CodeZine BOOKS)

  • 作者:松尾 愛賀
  • 発売日: 2016/04/15
  • メディア: 単行本(ソフトカバー)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com