最近はマイクロサービスを検討することが多いです。
マイクロサービスにおいて、よく問題となるのはサービス同士のやりとりをどのように行うかです。オーソドックスな選択肢としてはAPIを作成してHTTP通信で呼び出すという方法が候補に上がりますが、リクエスト数・量が増えてくれば、通信のオーバーヘッド、データ効率といった点で苦しくなってきます。
次に選択肢に上がるのはRPC、近年だとHTTP2を使用するgRPCでしょうか。
HTTP通信と比べると通信のオーバーヘッドも少なく、データ効率が良いため頻繁にやりとりが必要となるマイクロサービスとの相性が良いとされています。ただ、RubyでgRPCを使う...という話をあまり聞いたことなく試したことがなかったです。
今後、一つの選択肢としてgRPCが使えるように簡単なコードを動かしてみました。
実装の流れ
サンプルがgRPCの公式レポジトリにて公開されているのですが、初めて触るにはややオーバースペックかなと感じたので、最小限のシンプルな実装を行いました。 以下にRubyでgRPCを使ったサーバー・クライアントを実装するまでの流れを記載します。
.proto
ファイルの作成とデータ定義protoc
によるコード生成- サーバーの実装
- クライアントの実装
なお、実装にあたり「さくらのナレッジ」にて公開されている内容を参考にさせて頂きました。
素敵な記事をありがとうございます。とても内容が分かりやすかったです。
プロジェクトの作成
適当なディレクトリを作成してからbundle init
でGemfileを作成してください。
Gemfileには以下を貼り付けておけばOKです。
# frozen_string_literal: true source "https://rubygems.org" # gem "rails" gem "grpc" gem "grpc-tools"
bundle install
を実行後、lib
とprotos
ディレクトリを作成すれば完了です。
├── Gemfile ├── Gemfile.lock ├── /lib └── /protos
.protoファイルの作成
まずは拡張子が.proto
のファイルを作成します。
今回はユーザー情報に関するサービスを作成するためuser.proto
としました。.proto
ファイルの書き方については長くなるため省略します。先ほど添付した「さくらナレッジ」の記事を参照ください。
proto/user.proto
syntax = "proto3"; message Picture { uint32 id = 1; uint32 width = 2; uint32 height = 3; enum PictureType { PNG = 0; JPEG = 1; GIF = 2; } PictureType type = 4; } message User { uint32 id = 1; string nickname = 2; string mail_address = 3; enum UserType { NORMAL = 0; ADMINISTRATOR = 1; GUEST = 2; DISABLED = 3; } UserType user_type = 4; repeated Picture user_icon = 5; uint32 default_picture = 6; } message UserRequest { uint32 id = 1; } message UserResponse { bool error = 1; string message = 2; User user = 3; } service UserManager { rpc get (UserRequest) returns (UserResponse) {} }
特に重要となるのは下から3行のservice
に関する定義です。
ここではUserManager
がリクエストで受け取ったid
を持つユーザー情報を返すRPCを定義しています。
後にこのサービスを実装することになります。つまり、必要なサービスはこの時点で.proto
ファイルに定義されているということになります。
protoc によるコード生成
protoc
コマンドを使用することで.proto
から型情報などを持つファイルを自動生成することができます。
コマンドは事前にインストールが必要でした。自分はMacbookなのでbrew
を使ってインストールしました。
$ brew install protobuf
Protocol Buffer Compiler Installation | gRPC
今回はRubyのファイルを作成したいのでprotoc
コマンドの--ruby_out
オプションを指定する必要があります。
以下のコマンドはproto
ディレクトリ以下のuser.proto
をRubyのファイルとしてlib
ディレクトリに吐き出してねという指定をしています。
$ bundle exec grpc_tools_ruby_protoc -I ./protos --ruby_out=lib --grpc_out=lib ./protos/user.proto
実行後、lib
ディレクトリ直下に2つのファイルが作成されます。
- user_pb.rb
- user_services_pb.rb
Rubyの場合にはuser_pb.rb
には型の情報が。user_services_pb.rb
にはサービスの情報がクラスに変換されています。
lib/user_services_pb.rb
module UserManager class Service include ::GRPC::GenericService self.marshal_class_method = :encode self.unmarshal_class_method = :decode self.service_name = 'UserManager' rpc :get, ::UserRequest, ::UserResponse end Stub = Service.rpc_stub_class end
これで無事にコードの生成が完了しました。
サーバーの実装
作成したサービス・型情報を使用してサーバー側の実装をします。
クラス名は適当にServerImpl
としました。重要なのは先ほど出力したサービス情報UserManager::Service
クラスを継承することです。
grpc_server.rb
# frozen_string_literal: true require_relative './lib/user_services_pb' class ServerImpl < UserManager::Service def get(user_request, _call) user = User.new( id: user_request.id, nickname: 'Michael', mail_address: 'example.com', user_type: 0, user_icon: [ Picture.new(id: 1, width: 100, height: 100, type: 0), Picture.new(id: 2, width: 100, height: 100, type: 1), Picture.new(id: 3, width: 100, height: 100, type: 2), ], default_picture: 1, ) UserResponse.new(error: false, message: '', user: user) end end
そして.proto
ファイルの指定に従ってRPCであるget
を定義します。
このRPCはUserRequest
を受け取りUserResponse
を返す必要があるので、型を満たすように関数を定義します。
やはり、こういった点で動的型付け言語との相性が悪いなぁ...と感じますね。
同ファイルにサーバー起動の処理も追記しておきます。
port = '0.0.0.0:50051' s = GRPC::RpcServer.new s.add_http2_port(port, :this_port_is_insecure) GRPC.logger.info("... running insecurely on #{port}") s.handle(ServerImpl.new) s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
クライアントの実装
最後にクライアントを実装します。
サーバー側と同じように出力したサービス情報を利用してクライアントを作成するのみです。
リクエストパラメーターは同じように型を満たす必要があります。
# frozen_string_literal: true require_relative './lib/user_services_pb' user_stub = UserManager::Stub.new('localhost:50051', :this_channel_is_insecure) resp = user_stub.get(UserRequest.new(id: 1)) puts resp.class puts resp
動作確認
これで全ての実装が完了したので、動作を確認してましょう。
サーバー側を立ち上げておきます。
$ bundle exec ruby grpc_server.rb
クライアント側のコードを実行して、サーバーへリクエストを行います。
$ bundle exec ruby grpc_server.rb UserResponse <UserResponse: error: false, message: "", user: <User: id: 1, nickname: "Michael", mail_address: "example.com", user_type: :NORMAL, user_icon: [ <Picture: id: 1, width: 100, height: 100, type: :PNG>, <Picture: id: 2, width: 100, height: 100, type: :JPEG>, <Picture: id: 3, width: 100, height: 100, type: :GIF> ], default_picture: 1 > >
無事にユーザー情報が取得できました!
感想
慣れない実装だったのでコードが動くまでに時間がかかってしまいました。
しかし、慣れてくれば.proto
ファイルの定義からコード実装までサクサクできるようになると思います。
型情報からコードを自動生成できるのは本当にありがたいですね。スキーマ駆動開発で開発が効率化されると言われれるのがよく分かりました。
ただ、個人的にはgRPCと動的型付け言語との相性は、やはり静的型付けと比べると劣ると感じました。
Rubyの場合、型がクラス情報として扱われるため仮にフィールドを指定し忘れていても、実行時までエラーになることはありません。
残る疑問はRailsとgPRCの相性はどうなんだろうという点です。
基本的にRubyでAPI...となるとRails上で構築されるものが多いため、gRPCは多くの場合Rails経由で扱うことになると思います。
今回はそこまで調べられていないので、また別の記事でトライしてみたいと思います。
少しでも「ええな〜」と思ったらはてなスター・はてなブックマーク・シェアを頂けると励みになります。