最近社内でElixirをひっそり布教しようとしている、瀧川です。
弊社の一部プロダクトでは、gRPCでGolangアプリケーションを呼び出す構成をとっています。
それを説明するためにハンズオンをしたので、その一連の流れをこちらにもつらつら書いていきます!
内容は、以下の画像みたいなのを作っていこうと思います。
必要なコードは記事中に載せるのでコピペで動くはずなので、ぜひ実際にやってみてください!
そもそもgRPCって?
Googleが開発している、RPC (Remote Procedure Call)を実現するためのプロトコルになります。 Protocol Buffersというシリアライズのフォーマットを用いて、通信(関数・引数・返り値)を定義する仕様となっています。
対応するプログラミング言語も豊富で、その定義をもとにクライアント(呼び出し側)・サーバ(呼び出される側)のソースコードの生成が可能で、安全で効率よくサーバ間通信を行うことができます。
今回の例に当てはめて簡単に表現すると「RailsからGolangのメソッドを普通に呼び出せる」といったニュアンスですね!(雑)
詳しくは以下リンクを参考にしてください。
公式サイト: Guides | gRPC
実践
事前にインストールしてください
以下をインストールしましょう。(versionは2019-03-27時点の筆者のもの)
- Ruby: 2.5.3
- Golang: 1.11.4
- protobuf: 3.7.0
リポジトリを作成しましょう
アプリケーションとしては、RailsのJSON APIサーバと、GolangのgRPCサーバの2つになりますが、今回は単一リポジトリで実装していきましょう!
ざっくりディレクトリ構成
grpc-sample ├ proto │ └ ProtocolBuffers定義 ├ server │ ├ Railsアプリ │ └ lib │ └ protoから自動生成 └ pinger ├ Golangアプリ └ lib └ protoから自動生成
$ mkdir grpc-sample $ cd grpc-sample
さっそくProtocol Buffersで通信を定義しましょう
肝であるProtocol Buffersで通信(関数・引数・返り値)を定義していきましょう。
「引数なしでテキストを返すPing関数」の定義が以下のようになります。
簡単ですね!
grpc-sample/proto/pinger.proto
syntax = "proto3"; package pinger; service Pinger { rpc Ping(Empty) returns (Pong) {} } message Empty {} message Pong { string text = 1; }
GolangのgRPCサーバ作りましょう
ディレクトリを作成
# cd grpc-sample $ mkdir pinger $ mkdir pinger/lib $ cd pinger
必要ライブラリをインストール
# gRPCサーバの実装に必要 $ go get -u google.golang.org/grpc # Protocol Buffersの定義からGolangのソースコードを生成に必要 $ go get -u github.com/golang/protobuf/protoc-gen-go
定義からGolangのソースコードを生成
いよいよ grpc-sample/proto/pinger.proto
を元にGolangのインタフェースのソースコードを生成しましょう!
以下のコマンドによって grpc-sample/pinger/lib/pinger.pb.go
が生成されます。
# cd grpc-sample $ protoc -I ./proto pinger.proto --go_out=plugins=grpc:./pinger/lib $ ls pinger/lib pinger.pb.go
生成したGolangのインタフェースを実装したサーバを作成
コードの中身としては、以下を実装しています。
- 生成されたインタフェース(Ping関数)を実装したStructを作成
- 生成された
pinger.RegisterPingerServer
関数でgRPCサーバと実装したStructを紐づけ - 5300番ポートでサーブ
grpc-sample/pinger/server.go
package main import ( "context" "log" "net" "./lib" "google.golang.org/grpc" ) func main() { listener, err := net.Listen("tcp", ":5300") if err != nil { log.Fatalf("failed to listen: %v\n", err) return } grpcSrv := grpc.NewServer() pinger.RegisterPingerServer(grpcSrv, &server{}) log.Printf("Pinger service is running!") grpcSrv.Serve(listener) } type server struct{} func (s *server) Ping(ctx context.Context, req *pinger.Empty) (*pinger.Pong, error) { pong := &pinger.Pong{ Text: "pong", } return pong, nil }
実行してみましょう!
# cd grpc-sample/pinger $ go run server.go # 2019/03/27 19:53:21 Pinger service is running!
動いてるみたいですね!
動作確認
ちゃんと動作してるか、Golangで呼び出してチェックしてみましょう。
package main import ( "context" "fmt" "os" "../lib" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:5300", grpc.WithInsecure()) if err != nil { fmt.Fprintf(os.Stderr, "grpc.Dial: %v\n", err) return } defer conn.Close() client := pinger.NewPingerClient(conn) req := &pinger.Empty{} pong, err := client.Ping(context.Background(), req) if err != nil { fmt.Fprintf(os.Stderr, "Ping: %v\n", err) return } fmt.Fprintf(os.Stdout, "Pong: %s\n", pong.Text) }
実行してみましょう!
※ 別ターミナル等で go run server.go
を実行するの忘れずに!
# cd grpc-sample/pinger $ go run examples/client.go # Pong: pong
大丈夫そうですね!
これでgRPCのサーバ実装は終了です。
若干のおまじないコードはあるかと思いますが、とても簡単に実装はできました。
Railsを実装しましょう
今回はRails5のAPIモードで作成しようと思います。
また、DB(Active Record)は 用意が面倒なので スキップします。
Railsをインストール
# cd grpc-sample $ mkdir server $ cd server $ bundle init $ echo 'gem "rails", ">=5.2.2.1"' >> Gemfile $ bundle install # -O でskip active record $ bundle exec rails new . --api -O $ bundle exec rails server # localhost:3000 にアクセスするとおなじみの画面がでますね!
gRPC定義からRubyのソースコードを生成
必要なGemをインストールしましょう。
# cd grpc-sample/server # 呼び出し時に必要 $ echo "gem 'grpc'" >> Gemfile # 定義からのソースコード生成に必要 $ echo "gem 'grpc-tools'" >> Gemfile $ bundle install $ bundle exec grpc_tools_ruby_protoc -I ../proto --ruby_out=lib --grpc_out=lib ../proto/pinger.proto $ ls lib # pinger_pb.rb pinger_services_pb.rb tasks
生成されました!
呼び出すコードをControllerに実装
Stub.new
でコネクションを貼って、あとはほぼオブジェクトへのメソッド呼び出しと同じように書けばOK。
grpc-sample/server/app/controllers/application_controller.rb
require 'pinger_services_pb.rb' require 'pinger_pb.rb' class ApplicationController < ActionController::API def ping pinger_stub = Pinger::Pinger::Stub.new('localhost:5300', :this_channel_is_insecure) pong = pinger_stub.ping(Pinger::Empty.new) render json: {pong: pong.text} end end
grpc-sample/server/config/routes
Rails.application.routes.draw do get 'ping', to: 'application#ping' end
通しで実行してみましょう
Golang gRPCサーバ起動
# cd grpc-sample/pinger $ go run server.go # 2019/03/27 19:53:21 Pinger service is running!
Rails JSON APIサーバ起動
# cd grpc-sample/server $ bundle exec rails server # Listening on tcp://localhost:3000
アクセスしてみると...
ほしい結果が返ってきました!
振り返り
再掲となりますが、以下のようなファイル構成で構築しました。
grpc-sample ├ proto │ └ ProtocolBuffers定義 ├ server │ ├ Railsアプリ │ └ lib │ └ protoから自動生成 └ pinger ├ Golangアプリ └ lib └ protoから自動生成
やった内容は以下の通りです。
proto
ディレクトリにProtocol Buffersの仕様に則した通信(関数・引数・返り値)を定義- Ruby用のソースコードと、Golang用のソースコードを
1.
のprotoファイルを元に生成 2.
で生成されたGolangのインタフェース定義に則してgRPCサーバを実装2.
で生成されたRubyのgRPCクライアント定義に則して呼び出すコードを実装
シンプルに動かすだけなら、そこまで複雑でなく、コード量も少ないというのが伝わりましたか?
APIのインタフェースを考えなくてよく、関数定義をすればよいというのは、開発をしていてもとても考える負荷が減って効率的だと感じます。
今後世の中で一層マイクロサービス化が進むと思いますが、その中でgRPCは重要な技術となると考えているので、みなさんトライしてみてください!