Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Ruby で gRPC クライアントを実装する

gRPC のクライアントを Ruby で実装する手順を備忘も兼ねて書いておく

まずは gem を入れる

  • Ruby で gRPC を利用するために grpc gem をインストール
    • これを入れることで、クライアントの実装に必要な class 群を利用できるようになる
gem 'grpc', '~> X.XX.X'

.proto ファイルの取得とコンパイル

  • .protoファイルの管理方法はいくつかあるが、詳細はこちらの記事を参照
  • .proto を rb ファイルにコンパイルするには grpc_tools という Gem を利用する
$ gem install grpc-tools
  • コンパイルのコマンドは以下のような感じ
$ grpc_tools_ruby_protoc -I ./proto \--ruby_out=lib --grpc_out=lib ./proto/hoge_proto/*.proto
  • -I オプションで .proto ファイルを探索するディレクトリを指定
  • --ruby_out オプションで、コンパイル後の出力先ディレクトリを指定
    • どこでもいいんだけど /lib が多いらしい
  • --grpc_out オプションの第一引数にはコンパイル後の stub となる class ファイルの出力先を、第二引数にはコンパイル対象の .proto ファイルのあるディレクトリを指定
  • コンパイルが成功すると、各 proto に対応する pb.rb ファイルと、それらを全てインポートしてmodule化した hoge_services_pb.rb ファイルが生成される
    •  この hoge_services_pb.rb に rpc メソッドの定義と Stub の定義があり、呼び出しにはこの class を利用していく形になる

クライアントスタブの実装

上記で準備は整ったので、ここから実際に実装していく。
まずは gRPC コールを行うために必要なスタブインスタンスを作成する。
スタブインスタンスの作成に関しては、/initializer 配下にファイルを作って、そこで new するというやり方もよく見るけど、グローバル変数に突っ込むのも嫌だったので、今回は model クラスを切ってシングルトンパターンを適用する方向で実装。
正直この辺のベストプラクティスはまだ模索中。

model/grpc/hoge_service_client.rb
require 'proto/hoge_services_pb'
require 'singleton'

# ここは環境ごとに設定ファイルから取ってくる作りにした方が良い
END_POINT = "localhost:50051"

class Grpc::HogeServiceClient
  include Singleton

  attr_reader :stub

  def initialize
    @stub = HogeProto::HogeService::Stub.new(END_POINT, :this_channel_is_insecure)
  end
end
  • Stubインスタンス作成の部分は、上記にも書いたように hoge_services_pb.rb に記載がある Stub を new してインスタンスを作成している
    • new の第一引数は gRPC サーバーのエンドポイントを指定
    • 第二引数は、rpc 通信時の暗号化用の引数で、ちゃんと運用する場合はSSL証明書のパスなどを指定することになる

実際に gRPC サーバーをコールする

上記で作成した Stub を使って実際に rpcコールしてみる

grpc/hoge.rb
class Grpc::Hoge
  def get_name(params)
    # Stub インスタンスを腹持ちするmodelのインスタンスを作成
    client = Grpc::HogeServiceClient.instance
    # rpc コールに必要なリクエストデータを作成
    req = get_name_request(params)
    # 必要があればメタデータも作成
    meta = Hash.new
    meta["x-app-id"] = "hogeghoge"
    meta["x-app-password"] = "fugafuga"

    begin
      # ここで rpc コール
      client.stub.get_name(req, {metadata: meta})
      res = "success"
    rescue GRPC::BadStatus => ex
      res = "error"
    rescue ex
      res = "unexpected error"
    end

    res
  end

  private

  def get_name_request(params)
    HogeProto::GetNameRequest.new(
      id: params[:id]
    )
  end
  • client.stub.get_name の部分で rpc コールしている
    • この呼び出しの結果がエラー(Status.Codes が OK ではない)の場合、exceptionが発生するので begin で囲んで エラーの場合は rescue するようにする
  • meta に関しては、gRPC コール時のメタデータのセットをしている
    • ここではIDとパスワードをセットしているが、共通で必要なものは stub を作る model 側でインスタンス変数としてイニシャライズした方が良さそう
    • これは普通に Hash 形式で値をセットすれば良いらしい
    • ただし、キーには大文字が使えないっぽいので注意

エラーハンドリングについて

先ほども書いたが、rpc呼び出しでエラーステータスのレスポンスが返ってきた場合、GRPC::BadStatus class の例外が発生する。
なので、必ず begin~rescue する必要がある。
また、サーバー側が error_details を詰めて返してきた場合は、それを取り出すための実装も必要になる。
参考:https://www.rubydoc.info/gems/grpc/GRPC/BadStatus

nakaryooo
主に Ruby と Go を書いているエンジニアです
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away