golangでgRPCを使ったAPIServerを作ってみる
はじめに
この記事は、 eureka Advent Calendar 2017 5日目の記事です。
こんにちは、エウレカでCTO(*Cat Tech Officer)を営む傍、サーバーサイドエンジニアとして
日々生計を立てている@marnie-eureです。
2015年にgRPCが発表されてから、2年が経ちましたね。
国内外の大手企業でのgRPCの事例も増えてきました。
これからのご時勢、gRPCの一つも使えないのでは、愛する猫たちを養っていくことも難しいかもしれない。
そんな危機感にかられたので、今回はふわふわっとgRPCを使ってAPIを作ってみようと思います。
- (注) CTO(Chief Tech Officer)は@kaneshinなので
猫以外の話
はそちらにお問い合わせください。
gRPC Basics
gRPC?
gRPCは、Googleによって開発されたRPCフレームワークです。
HTTP/2を使用した通信層(ProtocolBuffersでシリアライズ)とProtocolBuffers(標準)としたテンプレートコードの生成がセットで提供されています。
勿論、HTTP2のstreamもサポートしています。
gRPCのRPC方式は以下の通り。
- Unary RPC (1リクエスト1レスポンス)
- Server streaming RPC (1つのリクエストに複数レスポンス)
- Client streaming RPC (複数のリクエストに一つのレスポンス)
- Bidirectional streaming RPC(双方向)
対応言語/platformも幅広く
- C++
- go
- Ruby
- Android Java
- PHP
- Objective-C
等の複数言語をサポートしています。
実装してみる
eurekaでは主にgolangを採用してますので、golangで実装します
前準備
- gRPCをインストールします。
1 go get -u google.golang.org/grpc
- protoファイルからコード生成をするコンパイラ(protoc)をインストールします。
protocのダウンロードはos別にこちらから
私の環境がosx-x86_64なので今回は protoc-3.5.0-osx-x86_64.zip
を利用します。
PATHの通ったディレクトリに解凍したディレクトリの/bin
の中のバイナリを移してあげてください。
- protocのGo用のプラグインをインストールします。
1 go get -u github.com/golang/protobuf/protoc-gen-go
.proto
ファイルにインターフェースを定義する
gRPCをベースにした開発では、まずはIDLを使ってprotoにAPIの定義を書きます。
ProtocolBuffer以外もサポートはしているようですが、
ツール周りやドキュメントが一番手厚いし、標準に寄り添って行きたい民なので、今回はprotocolBufferで.proto
ファイルを作ります。
1234567891011121314 syntax = "proto3";
service Cat {
rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}
}
message GetMyCatMessage {
string target_cat = 1;
}
message MyCatResponse {
string name = 1;
string kind = 2;
}
proto3の型や各言語の型の対応はgoogleのドキュメント、基本のscalar型以外を使いたい場合はgoogle/protobufをimportする感じで。
.proto
ファイルからserver、client,interface等のコードを生成する
定義から各言語のベースとなるコードの自動生成をします。
protocコマンドを実行します。
1 protoc --go_out=plugins=grpc:../pb cat.proto
成功すると xxxx.pb.go
が生成されます。
xxxx.pb.go
ファイルには、protoで定義した以下が含まれています。
- request
- response
- client,serverのinterface
- registerMethod
プラットフォームまたいでも、同一の定義からこの辺のコードが生成できるのは楽ですね
proto3のscalar型がgoのtime型やint型などに対応してたら嬉しかったのですが、今時点では対応してなかったのがやや辛み…
protocによるdocument生成
protocでdocumentの生成もできます。わーい。
1 protoc --doc_out=html,index.html:./ proto/*.proto
pb.goファイルを参照してサーバーとクライアントの実装
生成されたpb.goに含まれるinterfaceに沿って、実処理とserverとclientを実装します
service (実処理)
作られたxxx.pb.go
のinterfaceを満たすように実装します。
12345 xxx.pb.go
type CatServer interface {
GetMyCat(context.Context, *GetMyCatMessage) (*MyCatResponse, error)
}
serviceって名称は、公式やprotoの呼称から取ってきただけなのでお好みで。
1234567891011121314151617181920212223242526272829 package service
import (
"context"
"errors"
pb "marnie_playground/grpc-sample/pb"
)
type MyCatService struct {
}
func (s *MyCatService) GetMyCat(ctx context.Context, message *pb.GetMyCatMessage) (*pb.MyCatResponse, error) {
switch message.TargetCat {
case "tama":
//たまはメインクーン
return &pb.MyCatResponse{
Name: "tama",
Kind: "mainecoon",
}, nil
case "mike":
//ミケはノルウェージャンフォレストキャット
return &pb.MyCatResponse{
Name: "mike",
Kind: "Norwegian Forest Cat",
}, nil
}
return nil, errors.New("Not Found YourCat")
}
server
gRPC関連で書く必要があるコードは
- port listen
- 作った実処理の登録,serve
だけです。interceptor chain等は用途に応じて。
1234567891011121314151617181920212223 package main
import (
"log"
"net"
pb "marnie_playground/grpc-sample/pb"
"marnie_playground/grpc-sample/service"
"google.golang.org/grpc"
)
func main() {
listenPort, err := net.Listen("tcp", ":19003")
if err != nil {
log.Fatalln(err)
}
server := grpc.NewServer()
catService := &service.MyCatService{}
// 実行したい実処理をseverに登録する
pb.RegisterCatServer(server, catService)
server.Serve(listenPort)
}
client
12345678910111213141516171819202122232425 package main
import (
"context"
"fmt"
"log"
pb "marnie_playground/grpc-sample/pb"
"google.golang.org/grpc"
)
func main() {
//sampleなのでwithInsecure
conn, err := grpc.Dial("127.0.0.1:19003", grpc.WithInsecure())
if err != nil {
log.Fatal("client connection error:", err)
}
defer conn.Close()
client := pb.NewCatClient(conn)
message := &pb.GetMyCatMessage{"tama"}
res, err := client.GetMyCat(context.TODO(), message)
fmt.Printf("result:%#v \n", res)
fmt.Printf("error::%#v \n", err)
}
FactoryMethodとClientInterfaceが提供されているので、Connection作成してメソッドを呼び出すだけです。
ビルド & テスト
出来あがったclientとserverをそれぞれgo build,実行すれば出来上がり。
123 ./client
result:&cat.MyCatResponse{Name:"tama", Kind:"mainecoon"}
error::<nil>
middleware(interceptor)
logging,auth,recovery的な物はinterceptorとかmiddleware的な物を作ってやれると
既存のプロジェクトからの移行もスムーズかなーと思っていたので、調べてみました。
interceptorは型定義されてますので、下記を満たすようなインターフェースで実装します。
12345 google.golang.org/grpc/interceptor.go
//UnaryRPCならこっち
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
//StreamRPCならこっち
type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
利用するRPC方式によってgrpc.UnaryServerInterceptor() ないし grpc.StreamInterceptor()
で
grpc.ServerOption
に変換すればOKです
大雑把なイメージは以下のような感じ。
12345678910 <br />func MiddlewareFunc(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)
// 処理かく
}
func main() {
//中略
middleware := &YourMiddleware{}
opt := []grpc.ServerOption{grpc.UnaryInterceptor(MiddlewareFunc)}
server := grpc.NewServer(opt...)
}
grpc-middleware に何個か実装されたmiddleware(cf. logrus,validator)がありますので、
独自のmiddlewareを実装する場合はこの辺参考にすれば良いのかなと思います
まとめ
- 主要な通信層の処理は提供されているので、HTTP/2関連の実装は必要ない。
- protoからボイラーテンプレートコードやドキュメントも作られるのは楽。
- middlewareも増えている。
コードの自動生成によって本来時間をかけるべき、機能実装に集中できる
HTTP/2による高速化/stream用途や、protobuff標準でのコード生成という所が包括的に提供されるといった恩恵を受けられるのは大きなメリットだなぁと感じます。
フルスタックフレームワークと較べるのは、主旨が異なるので要件や選定において重視する所次第って感じですが、マイクロフレームワークを使うような構成を検討していたり、ストリーミング通信が要件に含まれているのであれば採用するメリットははあると考えています
細々、下記のような気になる所はありましたが
- proto3がgoの型を全て網羅してるわけではなさそうなので、applicationLayerの既存コードの型合わない場合は変換層とか必要そう。
-
curlでポチッと、みたいなのが使えなくなったので、テストがちょっと大変。grpc-gatewayでも使うべきなのかしら。
まぁデメリットと言うほどではないかな〜って気も。
テスト手段やエコシステム、ミドルウェアは今後、充実していく気もするし、既に必要十分ではあると思うので。
あとがき
今回はチュートリアル的な所と周辺情報を通して基本的な流れと所感を書いてみましたが、
eurekaでは実際に一部のmicro serviceのgRPC導入/移行を進行しています。
(既存コードとの兼ね合いで記事までに間に合いませんでした。てへぺろ
go+gRPCでの開発やeurekaに興味が持てた方(猫愛があれば尚良)は、是非お気軽にお話に来てくだされば!
それでは、2017年も残り少ないですが、日々猫への感謝を忘れず、過ごしていきましょう!!
参考にした資料/Repsitory Thanks!
grpc goDoc
grpc.io
grpc-middleware
grpc-echosystem
google/protobuf
protocol-buffers docs
エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!
はじめに
この記事は、 eureka Advent Calendar 2017 5日目の記事です。
こんにちは、エウレカでCTO(*Cat Tech Officer)を営む傍、サーバーサイドエンジニアとして
日々生計を立てている@marnie-eureです。
2015年にgRPCが発表されてから、2年が経ちましたね。
国内外の大手企業でのgRPCの事例も増えてきました。
これからのご時勢、gRPCの一つも使えないのでは、愛する猫たちを養っていくことも難しいかもしれない。
そんな危機感にかられたので、今回はふわふわっとgRPCを使ってAPIを作ってみようと思います。
- (注) CTO(Chief Tech Officer)は@kaneshinなので
猫以外の話
はそちらにお問い合わせください。
gRPC Basics
gRPC?
gRPCは、Googleによって開発されたRPCフレームワークです。
HTTP/2を使用した通信層(ProtocolBuffersでシリアライズ)とProtocolBuffers(標準)としたテンプレートコードの生成がセットで提供されています。
勿論、HTTP2のstreamもサポートしています。
gRPCのRPC方式は以下の通り。
- Unary RPC (1リクエスト1レスポンス)
- Server streaming RPC (1つのリクエストに複数レスポンス)
- Client streaming RPC (複数のリクエストに一つのレスポンス)
- Bidirectional streaming RPC(双方向)
対応言語/platformも幅広く
- C++
- go
- Ruby
- Android Java
- PHP
- Objective-C
等の複数言語をサポートしています。
実装してみる
eurekaでは主にgolangを採用してますので、golangで実装します
前準備
- gRPCをインストールします。
1 | go get -u google.golang.org/grpc |
- protoファイルからコード生成をするコンパイラ(protoc)をインストールします。
protocのダウンロードはos別にこちらから
私の環境がosx-x86_64なので今回は protoc-3.5.0-osx-x86_64.zip
を利用します。
PATHの通ったディレクトリに解凍したディレクトリの/bin
の中のバイナリを移してあげてください。
- protocのGo用のプラグインをインストールします。
1 | go get -u github.com/golang/protobuf/protoc-gen-go |
.proto
ファイルにインターフェースを定義する
gRPCをベースにした開発では、まずはIDLを使ってprotoにAPIの定義を書きます。
ProtocolBuffer以外もサポートはしているようですが、
ツール周りやドキュメントが一番手厚いし、標準に寄り添って行きたい民なので、今回はprotocolBufferで.proto
ファイルを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | syntax = "proto3"; service Cat { rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {} } message GetMyCatMessage { string target_cat = 1; } message MyCatResponse { string name = 1; string kind = 2; } |
proto3の型や各言語の型の対応はgoogleのドキュメント、基本のscalar型以外を使いたい場合はgoogle/protobufをimportする感じで。
.proto
ファイルからserver、client,interface等のコードを生成する
定義から各言語のベースとなるコードの自動生成をします。
protocコマンドを実行します。
1 | protoc --go_out=plugins=grpc:../pb cat.proto |
成功すると xxxx.pb.go
が生成されます。
xxxx.pb.go
ファイルには、protoで定義した以下が含まれています。
- request
- response
- client,serverのinterface
- registerMethod
プラットフォームまたいでも、同一の定義からこの辺のコードが生成できるのは楽ですね
proto3のscalar型がgoのtime型やint型などに対応してたら嬉しかったのですが、今時点では対応してなかったのがやや辛み…
protocによるdocument生成
protocでdocumentの生成もできます。わーい。
1 | protoc --doc_out=html,index.html:./ proto/*.proto |
pb.goファイルを参照してサーバーとクライアントの実装
生成されたpb.goに含まれるinterfaceに沿って、実処理とserverとclientを実装します
service (実処理)
作られたxxx.pb.go
のinterfaceを満たすように実装します。
1 2 3 4 5 | xxx.pb.go type CatServer interface { GetMyCat(context.Context, *GetMyCatMessage) (*MyCatResponse, error) } |
serviceって名称は、公式やprotoの呼称から取ってきただけなのでお好みで。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package service import ( "context" "errors" pb "marnie_playground/grpc-sample/pb" ) type MyCatService struct { } func (s *MyCatService) GetMyCat(ctx context.Context, message *pb.GetMyCatMessage) (*pb.MyCatResponse, error) { switch message.TargetCat { case "tama": //たまはメインクーン return &pb.MyCatResponse{ Name: "tama", Kind: "mainecoon", }, nil case "mike": //ミケはノルウェージャンフォレストキャット return &pb.MyCatResponse{ Name: "mike", Kind: "Norwegian Forest Cat", }, nil } return nil, errors.New("Not Found YourCat") } |
server
gRPC関連で書く必要があるコードは
- port listen
- 作った実処理の登録,serve
だけです。interceptor chain等は用途に応じて。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package main import ( "log" "net" pb "marnie_playground/grpc-sample/pb" "marnie_playground/grpc-sample/service" "google.golang.org/grpc" ) func main() { listenPort, err := net.Listen("tcp", ":19003") if err != nil { log.Fatalln(err) } server := grpc.NewServer() catService := &service.MyCatService{} // 実行したい実処理をseverに登録する pb.RegisterCatServer(server, catService) server.Serve(listenPort) } |
client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package main import ( "context" "fmt" "log" pb "marnie_playground/grpc-sample/pb" "google.golang.org/grpc" ) func main() { //sampleなのでwithInsecure conn, err := grpc.Dial("127.0.0.1:19003", grpc.WithInsecure()) if err != nil { log.Fatal("client connection error:", err) } defer conn.Close() client := pb.NewCatClient(conn) message := &pb.GetMyCatMessage{"tama"} res, err := client.GetMyCat(context.TODO(), message) fmt.Printf("result:%#v \n", res) fmt.Printf("error::%#v \n", err) } |
FactoryMethodとClientInterfaceが提供されているので、Connection作成してメソッドを呼び出すだけです。
ビルド & テスト
出来あがったclientとserverをそれぞれgo build,実行すれば出来上がり。
1 2 3 | ./client result:&cat.MyCatResponse{Name:"tama", Kind:"mainecoon"} error::<nil> |
middleware(interceptor)
logging,auth,recovery的な物はinterceptorとかmiddleware的な物を作ってやれると
既存のプロジェクトからの移行もスムーズかなーと思っていたので、調べてみました。
interceptorは型定義されてますので、下記を満たすようなインターフェースで実装します。
1 2 3 4 5 | google.golang.org/grpc/interceptor.go //UnaryRPCならこっち type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error) //StreamRPCならこっち type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error |
利用するRPC方式によってgrpc.UnaryServerInterceptor() ないし grpc.StreamInterceptor()
で
grpc.ServerOption
に変換すればOKです
大雑把なイメージは以下のような感じ。
1 2 3 4 5 6 7 8 9 10 | <br />func MiddlewareFunc(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) // 処理かく } func main() { //中略 middleware := &YourMiddleware{} opt := []grpc.ServerOption{grpc.UnaryInterceptor(MiddlewareFunc)} server := grpc.NewServer(opt...) } |
grpc-middleware に何個か実装されたmiddleware(cf. logrus,validator)がありますので、
独自のmiddlewareを実装する場合はこの辺参考にすれば良いのかなと思います
まとめ
- 主要な通信層の処理は提供されているので、HTTP/2関連の実装は必要ない。
- protoからボイラーテンプレートコードやドキュメントも作られるのは楽。
- middlewareも増えている。
コードの自動生成によって本来時間をかけるべき、機能実装に集中できる
HTTP/2による高速化/stream用途や、protobuff標準でのコード生成という所が包括的に提供されるといった恩恵を受けられるのは大きなメリットだなぁと感じます。
フルスタックフレームワークと較べるのは、主旨が異なるので要件や選定において重視する所次第って感じですが、マイクロフレームワークを使うような構成を検討していたり、ストリーミング通信が要件に含まれているのであれば採用するメリットははあると考えています
細々、下記のような気になる所はありましたが
- proto3がgoの型を全て網羅してるわけではなさそうなので、applicationLayerの既存コードの型合わない場合は変換層とか必要そう。
-
curlでポチッと、みたいなのが使えなくなったので、テストがちょっと大変。grpc-gatewayでも使うべきなのかしら。
まぁデメリットと言うほどではないかな〜って気も。
テスト手段やエコシステム、ミドルウェアは今後、充実していく気もするし、既に必要十分ではあると思うので。
あとがき
今回はチュートリアル的な所と周辺情報を通して基本的な流れと所感を書いてみましたが、
eurekaでは実際に一部のmicro serviceのgRPC導入/移行を進行しています。
(既存コードとの兼ね合いで記事までに間に合いませんでした。てへぺろ
go+gRPCでの開発やeurekaに興味が持てた方(猫愛があれば尚良)は、是非お気軽にお話に来てくだされば!
それでは、2017年も残り少ないですが、日々猫への感謝を忘れず、過ごしていきましょう!!
参考にした資料/Repsitory Thanks!
grpc goDoc
grpc.io
grpc-middleware
grpc-echosystem
google/protobuf
protocol-buffers docs
エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!