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で実装して、こちらのレポジトリ
で公開しているので、ぜひ見て見てくだい。