Protocol BuffersでAPI通信 サーバー編【golang】　

## protocol buffersとは
API通信などで、Jsonのようなテキスト形式で通信を行わずに、protocol buffersはバイナリ形式で通信を行います。
proto file(事前にスキーマを定義するファイル)から、サーバー側とクライアント側に、シリアライズ・デシリアライズ用のファイルを書き出し、それらを利用してデータの送受信を行います。

## Proto fileの定義
今回は都道府県のリストを返すAPIを作成して行きたいと思います。
まず、以下のようにproto fileを定義して行きいます。

```protobuf:prefectures.proto

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のコードは[こちら](https://github.com/TakuSemba/cheddar/blob/master/protos/prefectures.proto)に上がっています。

## protoからgoのファイルを書き出す
先ほど定義したproto fileから、server側のシリアライズ・デシリアライズ用のファイルを書き出します。

今回はgoのfileを書き出したいますが、どの言語でも書き出すことができます。

```bash
$ brew install protobuf
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
$ protoc --proto_path=protos --go_out=proto protos/*.proto //<- fileの書き出し
```

実行すると[このような](https://github.com/TakuSemba/cheddar/blob/master/proto/prefectures.pb.go)goファイルが書き出されているのがわかると思います。


## APIを実装する

Prefectureのモデルを定義します。

```go:models/prefecture.go

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](https://github.com/labstack/echo)を使って実装したいと思います。

```go:services/prefecture.go

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を叩いて見て、意図した通りのバイナリが返却されているかをテストします。

```go:prefecture_test.go

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


```

以下のコマンドでテストを走らせることができます。

```bash
$ go test prefecture_test.go -v
```

最終的に、mongo + docker + golangで実装して、こちらの[レポジトリ](https://github.com/TakuSemba/camembert)
で公開しているので、ぜひ見て見てくだい。