protocol buffersとは
API通信などで、Jsonのようなテキスト形式で通信を行わずに、protocol buffersはバイナリ形式で通信を行います。
proto file(事前にスキーマを定義するファイル)から、サーバー側とクライアント側に、シリアライズ・デシリアライズ用のファイルを書き出し、それらを利用してデータの送受信を行います。
Proto fileの定義
今回は都道府県のリストを返すAPIを作成して行きたいと思います。
まず、以下のようにproto fileを定義して行きいます。
syntax = "proto3";
option java_package = "com.takusemba.gouda.protos";
option java_outer_classname = "Prefectures";
option go_package = "proto";
package prefectures;
message Prefecture {
  int64 id                   = 1; // ID
  string name                = 2; // 名前
  string romaji              = 3; // 読み方(ローマ字)
}
message GetPrefecturesResponse {
  repeated Prefecture prefectures = 1;
}
実際のprotoのコードはこちらに上がっています。
protoからgoのファイルを書き出す
先ほど定義したproto fileから、server側のシリアライズ・デシリアライズ用のファイルを書き出します。
今回はgoのfileを書き出したいますが、どの言語でも書き出すことができます。
$ brew install protobuf
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
$ protoc --proto_path=protos --go_out=proto protos/*.proto //<- fileの書き出し
実行するとこのようなgoファイルが書き出されているのがわかると思います。
APIを実装する
Prefectureのモデルを定義します。
type Prefecture struct {
	Id       int         `json:"id"        bson:"_id"`
	Name     string      `json:"name"      bson:"name"`
	Romaji   string      `json:"romaji"    bson:"romaji"`
}
type Prefectures struct {
	Prefectures 	[]Prefecture   `json:"prefectures"`
}
APIの実装はechoを使って実装したいと思います。
package main
import (
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	proto1 "github.com/golang/protobuf/proto"
	"net/http"
	"github.com/TakuSemba/camembert/models"
	"github.com/TakuSemba/camembert/proto"
)
func main() {
	e := echo.New()
	//middleware
	e.Use(middleware.Logger())
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
	}))
	
	e.GET("/prefectures", func(c echo.Context) error {
		// DBからPrefectureのリストと取ってくる
		prefectures, _ := models.GetPrefectures()
		res := ToGetPrefecturesResponse(prefectures)
		data, _ := proto1.Marshal(&res)
		return c.Blob(http.StatusOK, "application/protobuf", data)
	})
	
	e.Start(":80")
}
func ToGetPrefecturesResponse(prefectures []models.Prefecture) (res proto.GetPrefecturesResponse) {
	prefecturesProto := ToPrefectures(prefectures)
	res = proto.GetPrefecturesResponse{
		Prefectures: prefecturesProto,
	}
	return
}
func ToPrefectures(prefectures []models.Prefecture) (res []*proto.Prefecture) {
	for _, pref := range prefectures {
		prefProto := ToPrefecture(&pref)
		res = append(res, &prefProto)
	}
	return
}
func ToPrefecture(pref *models.Prefecture) (chProto proto.Prefecture) {
	chProto = proto.Prefecture{
		Id:        int64(pref.Id),
		Name:      pref.Name,
		Romaji:    pref.Romaji,
	}
	return
}
以下でgo serverを起動するとhttp://your_url/prefecture で都道府県リストのバイナリを返すようになります。
$ go run main.go
テストコードを書く
実際にAPIを叩いて見て、意図した通りのバイナリが返却されているかをテストします。
package services
import (
	"io/ioutil"
	"log"
	"net/http"
	"testing"
	proto1 "github.com/golang/protobuf/proto"
	"github.com/k0kubun/pp"
	"github.com/TakuSemba/camembert/proto"
)
const (
	reqUrl = "http://your_url"
)
func TestGetPrefectures(t *testing.T) {
	url := reqUrl + "/prefectures"
	log.Println("[TEST] ", url)
	resp, _ := http.Get(url)
	defer resp.Body.Close()
	resData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Log("error", err)
	}
	log.Println("Response:", resp.StatusCode)
	if resp.StatusCode == 200 {
		resProto := proto.GetPrefecturesResponse{}
		if err = proto1.Unmarshal(resData, &resProto); err != nil {
			log.Println("unmarshaling error :", err)
		}
		pp.Println(resProto)
	} else {
		resProto := proto.ErrorResult{}
		if err = proto1.Unmarshal(resData, &resProto); err != nil {
			log.Println("unmarshaling error :", err)
		}
		pp.Println(resProto)
	}
}
以下のコマンドでテストを走らせることができます。
$ go test prefecture_test.go -v
最終的に、mongo + docker + golangで実装して、こちらのレポジトリ
で公開しているので、ぜひ見て見てくだい。