What is it, naokirin?

RailsからgRPCのリクエストをしてみる

今回は、RailsからgRPCのリクエストしてみたので、その方法を書き残しておきます。

必要なGemのインストール

Railsプロジェクトの準備は済んでいるものとして、Gemをインストールします。

Gemfile に以下を追加して、 bundle install します。

gem 'grpc'
gem 'grpc-tools'

Protocol Buffers による定義

gRPCでは、Protocol Buffers(Protobuf)を利用して通信を行います。

ProtobufはJSONXMLのような構造化データのフォーマットです。
JSONXMLよりも高速に動作するようにでき、データがバイナリ形式のため、データサイズを小さく抑えることができるメリットがあります。

Protobufでは、以下のようにRPCの定義と、構造化データの定義をすることができます。

// proto/ping.proto

// Protobufのバージョン指定
syntax = "proto3";

// パッケージの定義
package ping;

// Goで使う場合の、Goでのパッケージ名
option go_package = "proto/ping";

// RPCメソッドの定義
service PingService {
  // Unary RPC
  rpc Ping(Empty) returns (Pong) {}

  // Server streaming RPC
  rpc PingStream(PingRequest) returns (stream Pong) {}
}

// RPCの送信/受信時のデータ構造の定義
message Empty {}

message PingRequest {
  int32 count = 1;
}

message Pong {
  string text = 1;
}

Protobufの定義から、Rubyのコードを生成する

Protobufの定義が済んだので、ここからRubyのコードを生成します。

コード生成には、 grpc_tools_ruby_protoc を実行します。

cd "$RAILS_DIR"
bundle exec grpc_tools_ruby_protoc \
  -I ../proto \
  --ruby_out=lib \
  --grpc_out=lib \
  ../proto/ping.proto

これにより、lib以下にコードが生成されます。

lib
└ ping_pb.rb
└ ping_services_pb.rb

gRPCクライアントとしてRailsから呼び出す

最後にRailsからgRPCサーバーを呼び出してみます。

class ApplicationController < ActionController::API

  # Unary RPC
  def ping
    pong = ping_service.ping(Ping::Empty.new)
    render json: {pong: pong.text}
  end

  # Server streaming RPC
  def ping_stream
    pong = ping_service.ping_stream(Ping::PingRequest.new(count: 3))
    render json: {pong: pong.map{ |p| p.text }.inspect}
  end

  private

  def ping_service
    @client ||= Ping::PingService::Stub.new('localhost:5300', :this_channel_is_insecure)
  end
end

その他

Gruf というGemがあるのですが、クライアントとして利用する場合には、そこまでメリットが強くなさそうだったため、今回は利用を見送りました。

まとめ

今回は、RailsをgRPCクライアントとして、gRPCの通信をする方法を試してみたので、まとめておきました。

Protobufの定義の仕方や、コード生成ができれば、呼び出し自体はそれなりに簡単にできるようです。

ただ、本番利用を想定すると、エラーハンドリングや、通信中のアプリケーション終了時の扱いなどを気をつける必要はありそうです。