AppEngine Standard Environmentでgrpc-gatewayが使いたいので、なんとかした


AppEngineでgRPCを使う場合 ClientはTCP Socketが使えるので問題ないんですが、Serverに関してはFlexible Enviromentを利用しなければ利用できないのでなんとかStandard Enviromentで とりあえず 使う方法のメモ

gRPCを使いたい理由

そもそもなんでgRPCが使いたいかというと
APIを作るときにサーバーを作ったらそれに合わせてクライアントを実装するというのがよくとられる開発スタイルだと思うのですが
開発が進むに連れてサーバーとクライアントにズレが出てきてしまうことがあるかもしれません
それを防ぐのに一番いい方法はサーバー作る人がクライアントも一緒に作ることなんですが
それはそれでサーバーをやる人の負担が増えるので
サーバーを実装したらクライアントを自動生成するのが理想だなと思ったためです。

gRPCは色んな言語のクライアントを自動生成してくれるので便利で
同じ様な物でswaggerがありますが、swaggerの定義ファイルは人間が書くものではなかったためgRPCをベースにしたいと思いました。

あとAppEngineがgRPCに対応することがあったらすぐ切り替えれるようにするためです。

環境

go: 1.6
appengine: 1.9.37
で試してます。

またJSONに変換する部分はgrpc-gatewayを利用します。
grpc-gatewayはgRPC clientを内包するJSON serverで
gRPC Serverの中継用のサーバです。
また swaggerの定義ファイルを生成できるので
クライアントの自動生成はswaggerを使います。

必要なツールとパッケージ

go get -u github.com/golang/protobuf/protoc-gen-go
go get -u github.com/gengo/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/gengo/grpc-gateway/protoc-gen-swagger
go get -u github.com/k2wanko/grpc-pipe

定義

以下の様な単純にメッセージを返すだけのサービスを作るとします。

echo_service.proto
syntax = "proto3";
option go_package = "echo";

// Echo Service
//
// Echo Service API consists of a single service which returns
// a message.
package echo;

// SimpleMessage represents a simple message sent to the Echo service.
message Message {
    string value = 1;
}

// Echo service responds to incoming echo requests.
service EchoService {
    // Echo method receives a simple message and returns it.
    //
    // The message posted as the id parameter will also be
    // returned.
    rpc Echo(Message) returns (Message);
}

以下のコマンドでサーバーのinterfaceとclientを作成します。

protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/gengo/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
--grpc-gateway_out=logtostderr=true:. \
--go_out=Mgoogle/api/annotations.proto=github.com/gengo/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:. \
echo_service.proto

実装

AppEngineで使うための実装です。

echo_service.go
type echo struct{}

func (*echo) Echo(ctx context.Context, m *pb.Message) (*pb.Message, error) {
    return m, nil
}
server.go

import(
  ...
  "github.com/k2wanko/grpc-pipe/gateway"
  pb "./echo"
)

func init() {
    s := gateway.New(context.Background())
    s.RegisterService(pb.RegisterEchoServiceServer, pb.RegisterEchoServiceHandler, new(echo))
    http.Handle("/", s)
}

goapp serveコマンドで起動してhttp://localhost:8080/echoにcurlで叩きます

curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"value":"Hi"}'  http://localhost:8080/echo

完全なサンプルはgithubにあります。
SwaggerUIappengine.NewContextを挟むサンプルにもなってるのでAppEngineのサービスも利用できます。

grpc-pipeの仕組み

grpc-pipeが何をやってるかというと
単純にnet.Pipeを使ってメモリ内でクライアントとサーバーを繋いでます
なのでオーバーヘッドが気になります。

まとめ

ちゃんとgenerator書いて直接サーバーの実装を呼び出せるようにしようと思ってるので
時間が空いたらやってみます