はてなブログとのクロスポストです。
gRPCについては以前から興味があっていくつか記事を斜め読みしてましたが、結局自分で動かしてみないと分からないため、Rubyで動かしてみようと思います。
gRPCとは?
Googleの内部で使われていたRPCで、メッセージのシリアライズ方式としてProtocol Bufferを使う。HTTP/2ベース。
Client / Server の内部実装を問わないので、gRPC ServerをGoで書いて、Rubyから使うといったことが可能。
より詳しい説明は公式のWhat is gRPC?を見るべし。
Quick Start
gRPCがオフィシャルにRubyによるQuick Startを提供しているので、まずはここから始めました。
お約束のHello Worldです。
[https://grpc.io/docs/quickstart/ruby.html:embed:cite]
.proto ファイルにserviceを定義し、grpc_tools_ruby_protoc コマンドでRubyのコードをジェネレートするというgRPCのお作法が分かったので、次は自分で(しょうもない)マイクロサービスを作ってみることにしました。
マイクロサービスをつくる
ほぼHelloworldと大差ないですが、オウム返しをするParroterというサービスをつくってみることにしました。
.proto
リクエストとして文字列msgを受け取り、レスポンスはmsgとその回数countを返します。
syntax = "proto3";
package parroter;
service ParrotService {
rpc say(ParrotRequest) returns (ParrotResponse) {}
}
message ParrotRequest {
string msg = 1;
}
message ParrotResponse {
string msg = 1;
int32 count = 2;
}
.protoからRubyコードを出力します。
grpc_tools_ruby_protoc -Iproto --ruby_out=lib --grpc_out=lib proto/parroter.proto
とりあえず生成されたファイルをrequireすれば、gRPC Client / Server をつくれるのですが、できればインターフェースと実装の分離をしておきたい。
[https://shiladitya-bits.github.io/Building-Microservices-from-scratch-using-gRPC-on-Ruby/:title] に書かれているように、private な
gem を生成するのがよさそうです。ディレクトリレイアウトは以下のようになりました。
$ tree .
.
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│ ├── console
│ └── setup
├── lib
│ ├── parroter
│ │ └── version.rb
│ ├── parroter.rb
│ ├── parroter_pb.rb
│ └── parroter_services_pb.rb
├── parroter.gemspec
└── proto
└── parroter.proto
[https://github.com/kotaroito/grpc-parroter-service]
gRPC Server
作成した private な gem を使って、[https://shiladitya-bits.github.io/Building-Microservices-from-scratch-using-gRPC-on-Ruby] を参考にしながら、gRPC Serverを作成してみました。
Gemfile
source 'https://rubygems.org'
gem 'parroter',:git => "https://github.com/kotaroito/grpc-parroter-service",:branch => 'master'
gem 'grpc', '1.7.0.pre1'
bin/start_server.rb
# !/usr/bin/env ruby
require 'grpc'
require 'parroter_services_pb'
class ParrotServer
class << self
def start
start_grpc_server
end
private
def start_grpc_server
@server = GRPC::RpcServer.new
@server.add_http2_port("0.0.0.0:50052", :this_port_is_insecure)
@server.handle(ParrotService)
@server.run_till_terminated
end
end
end
class ParrotService < Parroter::ParrotService::Service
def initialize
@count = {}
end
def say(parrot_req, _unused_call)
p parrot_req
Parroter::ParrotResponse.new(msg: parrot_req.msg, count: count_msg(parrot_req.msg))
end
private
def count_msg(msg)
@count[msg] = 0 unless @count[msg]
@count[msg] += 1
end
end
ParrotServer.start
bin/test_parrot_service
# !/usr/bin/env ruby
require 'grpc'
require 'parroter_services_pb'
def test_single_call
stub = Parroter::ParrotService::Stub.new('0.0.0.0:50052', :this_channel_is_insecure)
req = Parroter::ParrotRequest.new(msg: 'Hello gRPC.')
resp_obj = stub.say(req)
p resp_obj
end
test_single_call
gRPC Serverを起動後に、何度かClientを実行すると...
$ bundle exec bin/test_parrot_service
<Parroter::ParrotResponse: msg: "Hello gRPC.", count: 1>
$ bundle exec bin/test_parrot_service
<Parroter::ParrotResponse: msg: "Hello gRPC.", count: 2>
こうなります。
気になることをつらつらと
RubyでgRPC Serverを書けそうなことは分かったので、気になることをいくつか調べてみました。
2017年10月現在、(Rubyで書くなら)サーバーは"grpc" gemに同梱されているGRPC::RpcServer 一択だと思われます。
充実したドキュメントはなく、設定オプションを知りたければGithubのソースを追いかけるのがよさそう。
[https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/rpc_server.rb#L205-L210] を読むと、スレッドのpool_size や poll_periodなどが設定できそうです。
Interceptor
Rack Middleware に相当するものは、gRPCだとInterceptorと呼ばれるようです。
grpc gemの1.6.7にはありませんでしたが、1.7.0.pre1 から実装されていました。
ただし、EXPERIMENTAL API とのこと。
[https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/generic/rpc_server.rb#L200-L203]
すごく簡素ですが、Interceptorを使ってみました。
class ParrotServer
class << self
def start
start_grpc_server
end
private
def start_grpc_server
@server = GRPC::RpcServer.new(interceptors:[HelloInterceptor.new])
@server.add_http2_port("0.0.0.0:50052", :this_port_is_insecure)
@server.handle(ParrotService)
@server.run_till_terminated
end
end
end
class ParrotService < Parroter::ParrotService::Service
...snip...
end
class HelloInterceptor < ::GRPC::ServerInterceptor
def request_response(request:, call:, method:)
p "Received request/response call at method #{method}" \
" with request #{request} for call #{call}"
call.output_metadata[:interc] = 'from_request_response'
p "[GRPC::Ok] (#{method.owner.name}.#{method.name})"
yield
end
end
ParrotServer.start
感想
gRPCはインターフェースを明確に宣言でき、しかもカンタンなのが良い。
JSON Schemaの経験があるだけに余計に。。。
現時点では詳しいドキュメントないので、ソースコードを読みさえすれば、Rubyでもプロダクションで使えるgRPC Serverを書けそうな気がしてる。
参考資料
- [https://shiladitya-bits.github.io/Building-Microservices-from-scratch-using-gRPC-on-Ruby]
- https://speakerdeck.com/kazegusuri/go-conference-2016-spring