LoginSignup
5
2

More than 3 years have passed since last update.

railsにgRPCクラアント導入

Last updated at Posted at 2020-07-14

経緯

会社でPMを担当しているプロダクト(Rails)の実装で他のサービスから情報を取得する必要があり、そのサービスがOpenAPIでなく、gRPCでAPIインタフェースが定義されていたので、gRPCのクライアントを実装しました。
僕が前職でrailsでのgRPC周りを少し触っていたので、相対的に僕がやった方がチームの開発工数が増えると思ったので自分でやることにしました。
それについてのアウトプットです。

実装手順

1. gem導入

※前職で使っていたgrufというライブラリを使いました。

Gemfile

gem "google-protobuf" 
gem "grpc-tools"
gem "gruf"

※ webというコンテナで開発してる

docker-compose run --rm web bundle install
2. protoファイルが管理されているリポジトリをsubmoduleでアプリケーションのサブディレクトリとして登録して、protoファイルを元のリポジトリから取ってくる
$ git submodule add [web URL or ssh key] proto
$ git submodule init
$ cd proto
$ git submodule update
3. protoファイルをrubyファイルにコンパイル
docker-compose run --rm web grpc_tools_ruby_protoc -I [コンパイル対象のprotoディレクトリ] --ruby_out=[コンパイル後のファイル保存先のディレクトリ] --grpc_out=[コンパイル後のファイル保存先のディレクトリ] [コンパイル対象のprotoディレクトリ内の対象ファイル]
4. コンパイル後のrubyファイル(*_pb.rb)の読み込み設定

コンパイル後のファイルは、ファイル名と、クラス名が噛み合っておらず、Railsの読み込み規則に則っていなく自動読み込まれないので、指定してあげる必要がある。

config/initializers/gruf.rb
require "gruf"

Gruf.configure do
  Dir.glob(Rails.root.join("[コンパイル後のファイル保存先のディレクトリ]/*_pb.rb")).each do |file|
    require file
  end
end

コンパイル後のファイルでは下記のように自動で指定されており、auto_load_pathに追加しておく必要がある。
e.g. gruf-demoから
require 'Products_pb'

※コンパイル後のファイルは基本修正しないので。

config/application.rb
  class Application < Rails::Application
    config.paths.add [コンパイル後のrubyファイルディレクトリ], eager_load: true
  end
5. クライアントがサーバをコールする部分の実装

ここまでで、全てのコンパイル後のrubyファイルは使えるようになったでのクライアントの実装。

moduleにしようかとか悩みましたが、既存の実装でクライアント系の処理はservice層にまとめていたので、今回もそれに習う形にしました。

※特にmetadata周りはサーバ側の実装に依存するので注意。
grufのwikiだと、クライアントの初期化(Gruf::Client.new)時のoptions引数のキーでusernameを入れていたりとこの辺が今回の実装と違っており、若干悩みました。

app/services/grpc_client_service.rb

class GrpcClientService
  def initialize
    @metadata = {
      login: ENV["GRPC_CLIENT"],
      password: ENV["GRPC_PASSWORD"]
    }
  end

  def run(service_klass, method, request)
    client = Gruf::Client.new(
      service: service_klass,
      options: {
        hostname: ENV["GRPC_HOST"],
        channel_credentials: :this_channel_is_insecure
      }
    )

    client.call(method, request.to_h, @metadata)
  end
end

導入してみての感想とか

前職でgrufは使っていたので、余裕かと思っていましたが、やはり導入するのと、ただ使うだけでは結構違うなと思いました。
しっかり設定周りのコードを読んでおけば良かったと後悔してます。

また、今回gRPCサーバがgoで書かれており、クライアントからのコールがうまく行かないときにコード読むのに苦労して結局諦めたので、goも勉強したいなと思いました。

また少し詰まったのは、クライアントの初期化(Gruf::Client.new)時にmetadataを入れると謎の勘違いしており(本当はcall時の引数に入れる)、ライブラリのWikiに書いてないことはやはり、しっかりコード読まないといけないなと初歩的なことを改めて実感しました。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2