小さなエンドウ豆

まだまだいろいろ勉強中

Rails アプリケーションから gRPC サーバー に接続するまで

Rails アプリケーションから gRPC サーバーに接続するまで

そもそも RPC とは

あるコンピュータで動作するソフトウェアから、通信回線やコンピュータネットワークを通じて別のコンピュータ上で動作するソフトウェアへ処理を依頼したり、結果を返したりするための規約。

要するに別アプリにある処理(関数)を呼び出すこと。
API はこれに当たるのかなと思っています。

gRPC とは

gRPCはRPCを実現するためにGoogleが開発したプロトコルの1つで、インターフェイス定義言語のもとになるメッセージ交換形式として Protocol Buffers を利用できます。gRPC上のアプリケーションでは、別マシン上にあるアプリケーションのメソッドをローカルオブジェクトのように直接呼び出すことができ、分散アプリケーションおよびサービスの作成を簡単にできます。

Protocol Buffers が現時点ではわかりませんが、RPC を実現するためのプロトコルGoogle が定めたものという認識でいます。

Protocol Buffers

インターフェース定義言語(IDL)でデータ構造を定義する通信や永続化での利用を目的としたシリアライズフォーマットであり、Googleにより開発されています。

Protoc インストール

protoc は protocol buffers で定義されるスキーマから構造体やクラスを生成するコンパイラ
Mac では homebrew でインストールが可能みたいだけどなぜか出来なかったのでソースコード落としてきてビルドしました。

github.com

解凍後、make コマンド実行!

Go で gRPC サーバーを実装

まずは実装例が多い Go 言語で書いてみたいと思います。
Go をあまり書いたことがないため少々時間がかかりましたが、下記の記事を参考に作りました。 記事中ではリクエストで受けた文字列をそのまま返す GetEcho という関数を実装しています。

qiita.com

$ go run client.go
result:&echoService.EchoResponse{Input:"hiracky16", XXX_NoUnkeyedLiteral:struct {}{}, XXX_unrecognized:[]uint8(nil), XXX_sizecache:0}
error::<nil>

成功したっぽい。

Rails から gRPC サーバーに問い合わせる

ここからが本題です。 今度は Rails サーバーから GetEcho を呼び出します。

まず Gemfile に 2 つの gem を追加します。 grpc は Ruby で grpc を実現されるための gem、
grpc-tool は参考記事で作成した Protocol Buffers を Ruby のクラスに変換するツールです。

gem 'grpc'
gem 'grpc-tools'

次に以下のコマンドを実行し、

$ bundle exec grpc_tools_ruby_protoc -I ../service --ruby_out=lib --grpc_out=lib ../service/*.proto

これによって Rails プロジェクトの lib 配下に echoService_pb.rbechoService_services_pb.rb が生成されます。
(proto ファイルがキャメルケースだったためファイル名までそうなってしまった。。)

簡単に説明すると echoService_pb.rb では文字列やシンボルベースで proto ファイルの内容からリクエストとレスポンスのクラス(GetEchoMessage, EchoResponse)を作っていた。
要するに Ruby で proto ファイルの内容を表現したものです。
それらのクラスを使って echoService_service_pb.rb では Echo という module の形で get_echo というメソッドを提供しています。

echoService_service_pb.rb の内容は以下です。

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# Source: echoService.proto for package ''

require 'grpc'
require 'echoService_pb'

module Echo
  class Service

    include GRPC::GenericService

    self.marshal_class_method = :encode
    self.unmarshal_class_method = :decode
    self.service_name = 'Echo'

    rpc :GetEcho, GetEchoMessage, EchoResponse
  end

  Stub = Service.rpc_stub_class
end

これをコントローラーから呼び出します。

require 'echoService_services_pb.rb'
require 'echoService_pb.rb'

class ApplicationController < ActionController::API
  def echo
    echo_stub = Echo::Stub.new('localhost:2525', :this_channel_is_insecure)
    res = echo_stub.get_echo(Echo::Service::GetEchoMessage.new(target_echo: 'test'))
    render json: { message: res.input }
  end
end
  1. echoService_service_pb.rb に記述のあった stub のインスタンスを生成
  2. 引数には GetEchoMessage のインスタンスを new して上げて、get_echo(スネークケースなので注意)を呼ぶ
  3. レスポンスに EchoResponse のインスタンスが返ってくるので proto ファイルで指定のあった input というキーの値を取り出して返す

まとめ

やっと gRPC について勉強することができました。 Protocol Buffers を使ってスキーマを定義しておくといろいろなサービスや言語で使い回すことができるので、マイクロサービス設計には欠かせないのかもと思いました。

また、たとえ proto ファイルの内容が変更された場合でも生成し直せば動くことが期待されるため、スキーマの変更に強いのも使われている要因なのかなと思います。

プライベートで gRPC を使うことはそうそうないかもですが、チーム開発では便利なので使える場面があったら試してみたいです。