【gRPC】gRPCの基本的な使い方。Rubyのチュートリアルから必要なファイルや設定順序を理解する。

gRPCの使い方まとめ。protoファイルとサービス定義ファイルの関係などgRPCのチュートリアルを流し読みするだけではよくわからなかったので、一つづつ確認してみる。

gRPC公式のチュートリアル（ruby）を参照。

##目次

1. [gRPCとgRPCツールのインストール](#1-grpcとgrpcツールのインストール)
2. [サンプルディレクトリのクローン](#2-サンプルディレクトリのクローン)
3. [サービス定義ファイルの作成](#3-サービス定義ファイルの作成)
4. [rubyファイルをコンパイル](#4-rubyファイルをコンパイル)
5. [サーバーの作成](#5-サーバーの作成)
6. [クライアントの作成](#6-クライアントの作成)
7. [サーバーの起動](#7-サーバーの起動)
8. [メソッドの実行](#8-メソッドの実行)
9. 参考リンク


####▼Point

- １つのサービス定義ファイル（.proto）から各言語の２つのファイルが作成される。
- 言語はコンパイル時に指定する。
- No5のサーバーの作成以降はRubyの処理


<br>
##1. gRPCとgRPCツールのインストール

gRPCを利用するためにgRPCをインストールする。
併せて、.protoファイルから各言語にコンパイル（自動翻訳）されたコードを生成できる便利ツールgRPC-toolsをインストールする。

```:
$ gem install grpc
$ gem install grpc-tools
```


>＞権限なしエラーがでた場合（You don't have write permissions）
`sudo`をつけて実行し、PWを入力する。


<br>
##2. サンプルディレクトリのクローン

```
$ git clone -b v1.34.0 https://github.com/grpc/grpc
$ cd grpc


$ cd examples/ruby/route_guide
```

route_guideフォルダにはrubyのclientとserverファイルが存在する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/89cf13c6-3b36-f987-6cea-e88513b0e821.png)


<br>
##3. サービス定義ファイルの作成

###サービスの定義
.protoファイルにgrpcのサービス（rpc）と各要求と応答の変数のタイプ（型）を定義する。

>サービスとは？
rpcで定義したメソッドのこと。grpcに対して指定のメソッドで要求し、応答を得る機能のひとまとまりサービスと呼んでいる。

```.proto:サービスの定義
service RouteGuide {
  rpc GetFeature(Point) returns (Feature) {}
  rpc ListFeatures(Rectangle) returns (stream Feature) {}
  rpc RecordRoute(stream Point) returns (RouteSummary) {}
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
```

サービス名RouteGuideの中に４つのメソッドが定義されている。



<br>
###メソッド
gprcサーバーに対して要求する内容がメソッドになる。

`rpc メソッド名(渡すメッセージ名) returns(受け取るメッセージ名)`

- 冒頭は`rpc`で記述
- 引数にメッセージ型名をいれる
    - メッセージの詳細はファイル下部に定義

メッセージとは変数名と型を指定したもの。


<br>
**メソッドの例**

```.proto:
rpc GetFeature(Point) returns (Feature) {}
```

- メソッド名： `GetFeature`
- 要求のメッセージ型： `Point`
- 応答のメッセージ型： `Feature`

<br>
**▼対応するメッセージ**

```.proto:message
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

message Feature {
  string name = 1;
  Point location = 2;
}
```

`Point`では、`latitude`と`longitude`の２つの変数を渡す。型はどちらも整数。


`Feature`では文字列の`name`と、`latitude`と`longitude`がハッシュとして格納された`location`を受け取る。（messageの中の型として、別のmessageを使用したパターン）

- `location = {latitude:"値", longitude:"値"}`


<br>
###syntaxとpackage

```.proto:
syntax = "proto3";

package routeguide;
```

**・`syntax = "構文名"`**
`syntax`はproto buffersのバージョンを記述する。google推奨はproto3。

デフォルトはproto2。proto2とproto3は記述方法に違いがあるため、`syntax = "proto3"`　は必須な記述。


**・`package パッケージ名`**
このprotoファイルの名前空間を定義。

他の.protoファイルで同じメッセージ名を使っている場合にそれそれを区別することができる。

他ファイルからの呼び出しは、`パッケージ名.メッセージ名`。

<br>
**▼packageの利用例**
例えば以下のようなpakcageとmessageが記述されている場合、他のファイルからこのメッセージを参照する時は、

`routeguide.Point`で指定できる。

```.proto:packageの利用例
package routeguide;

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}
```


<br>
###オプション
`option`で注釈を記述できる。

コードの意味を変更するものではないが、指定したオプションに沿って出力方法がわずかに変化したりする。


```.proto:
option java_multiple_files = true;
option java_package = "io.grpc.examples.routeguide";
option java_outer_classname = "RouteGuideProto";
```

いずれもjavaファイルを生成するときのみに適用される。
生成する際のファイル名などを指定している。



<br>
##4. rubyファイルをコンパイル
grpcのサービスと変数の型を指定した`.proto`ファイルから`.rb`にコンパイルしたファイルを生成する。

###4-1 gRPCを実行できるRubyをインストール

```:submoduleの初期化
#cloneしたsubmoduleを初期化
git submodule update --init
```

```:rvmとgpgのインストール
#rvmのインストール
##GPGキーのインストール
$ gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

##RVMのインストール（安定版）
$ \curl -sSL https://get.rvm.io | bash -s stable --ruby

##bundlerのインストール
gem install bundler
```


**・rvmとは？**
Ruby Version Managerの略でRubyのバージョン管理ツール。

HomebrewでRubyをインストールする場合はそのOSがサポートする最新のrubyを使うことになる。

これに対し、RVMを使えばアプリケーション毎にRubyのバージョンを管理できる。

>https://rvm.io/rvm/install



<br>

```:.protoを.rbにコンパイル
$ grpc_tools_ruby_protoc -I ../../protos --ruby_out=../lib --grpc_out=../lib ../../protos/route_guide.proto
```

・`grpc_tools_ruby_protoc`
RVMをインストールしたことで使えるコマンド

・ `-I ディレクトリパス`
プロトコル定義ファイル（.proto）があるディレクトリのパス

・ `--ruby_out=ディレクトリパス`
コンパイル後の.rbファイルを出力するディレクトリパス

・ `./../protos/route_guide.proto`
プロトコル定義ファイルのパス


###コンパイルの流れ

(1) サービス定義が書かれた、 protos > route_guide.protoファイルを指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/ef858918-5b8d-55f1-fe42-cb073af064ef.png)

(2) コンパイル後のファイル出力先として、 ruby > lib を指定。


(3) コンパイル後のファイル`route_guide_pb.rb`と`route_guide_services_pb.rb`の２ファイルが作成される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/c3f2dc70-a053-1382-d06a-bcfb61cebfb3.png)

**▼pbがつくファイル**
pbがつくファイルはProtocol Buffersで作成したPC用のファイルのため編集する必要はない。

ファイルに`DO NOT EDIT!`が記載してある。


>＞デフォルトのlibディレクトリ
クローン後のlibディレクトリには既にコンパイル後のファイルが存在するため、２つのファイルを削除してから、上記コマンドを実行すると、ファイルの作成が確認できる。
libディレクトリは出力用で必要。


####コンパイルされるファイルの種類
xxx.protoから生成された２つのファイルの違い。

(1) xxx_pb.rb
モジュールの定義。
メッセージ名と、各メッセージのタイプが記述されている。

```rb:コード例
Google::Protobuf::DescriptorPool.generated_pool.build do
  add_file("route_guide.proto", :syntax => :proto3) do

    #変数の型を定義
    add_message "routeguide.Point" do
      optional :latitude, :int32, 1
      optional :longitude, :int32, 2
    end 
    略
end


#メッセージ名を定義
module Routeguide
  Point = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("routeguide.Point").msgclass
  略
end
```


(2) xxx_services_pb.rb 
サービスクラスとスタブクラスを定義。

**・サービスクラスとは？**
定義したサービスを実装するときに基本クラスとして使用するクラス。

**・スタブクラスとは？**
サービスのインスタンスにアクセスするためのクラス。


```ruby:コード例
module Routeguide
  module RouteGuide
    
    class Service

      include GRPC::GenericService
      略

      #サービスクラス
      rpc :GetFeature, ::Routeguide::Point, ::Routeguide::Feature
      rpc :ListFeatures, ::Routeguide::Rectangle, stream(::Routeguide::Feature)
      rpc :RecordRoute, stream(::Routeguide::Point), ::Routeguide::RouteSummary
      rpc :RouteChat, stream(::Routeguide::RouteNote), stream(::Routeguide::RouteNote)
    end

    #スタブクラス
    Stub = Service.rpc_stub_class
  end
end
```


<br>
##5. サーバーの作成
ruby > route_guide > route_guide_server.rb　がサーバー用のファイル。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/92f892c7-8099-a353-a30d-3a0f65e51c6e.png)

###継承したクラスとメソッド
このサーバーファイルは、スーパークラスとして、コンパイルで生成された`xxx_services_pb.rb`のクラスを継承している。

```ruby:ruby:xxx_services.rb
class ServerImpl < RouteGuide::Service
```

このクラスの中で、rpcで定義した４つのメソッドが記載してある。

```ruby:xxx_services.rb
省略
  def get_feature(point, _call)
    name = @feature_db[{
      'longitude' => point.longitude,
      'latitude' => point.latitude }] || ''
    Feature.new(location: point, name: name)
  end
省略
```

この処理では、プロトコルバッファで定義したpointが渡され、Featureが返る。

```ruby:（参考）xxx_services_pb.rb
rpc :GetFeature, ::Routeguide::Point, ::Routeguide::Feature
```


###サーバーの起動
`GRPC::RpcServer`のインスタンスからポート番号`0.0.0.0:50051`でサーバーを起動する。

```ruby:ruby:xxx_services.rb
  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(feature_db))
  s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
```



<br>
##6. クライアントの作成
ruby > route_guide > route_guide_client.rb　がクライアント用のファイル。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/4f141e04-de9c-ea65-3a38-e92c38674c38.png)

```ruby:route_guide_client.rb
#Pointのインスタンスを作成し定数に代入
GET_FEATURE_POINTS = [
  Point.new(latitude:  409_146_138, longitude: -746_188_906),
  Point.new(latitude:  0, longitude: 0)
]


def run_get_feature(stub)
 GET_FEATURE_POINTS.each do |pt|
    resp = stub.get_feature(pt)
    if resp.name != ''
      p "- found '#{resp.name}' at #{pt.inspect}"
    else
      p "- found nothing at #{pt.inspect}"
    end
  end
end
```

xxx_services_pb.rbから作成したstubインスタンスを、メソッドの引数として渡す。

```ruby:stub
def main
  stub = RouteGuide::Stub.new('localhost:50051', :this_channel_is_insecure)
  run_get_feature(stub)
  run_list_features(stub)
  run_route_chat(stub)
  if ARGV.length == 0
    p 'no feature database; skipping record_route'
    exit
  end
  raw_data = []
  File.open(ARGV[0]) do |f|
    raw_data = MultiJson.load(f.read)
  end
  run_record_route(stub, raw_data)
end
```



<br>
##7. サーバーの起動

```
#rubyのディレクトリに移動
$ cd examples/ruby

#必要なgemをインストール
$ gem install bundler && bundle install

#サーバーを起動
$ bundle exec route_guide/route_guide_server.rb ../python/route_guide/route_guide_db.json
```

・`gem install bundler`
rubyのパッケージの一つ。gemのバージョン毎の互換性が保てるよう必要gemをインストールする。

インストールすることで`bundle`コマンドが使えるようになる。

プロジェクト毎にインストールする。

・`bundle install`
現在インストールされているgemを動かすために必要なgemやバージョンを検証し一括インストールする。

・`bundle exec`
bundlerを使って実行するコマンド。
これがないと、グローバルなgemのバージョンが適用されてしまう。

サーバーの定義が書かれた.rbファイルを実行し、データが書かれたjsonファイルを読み込む。


```json:route_guide_db.json
[{
    "location": {
        "latitude": 407838351,
        "longitude": -746143763
    },
    "name": "Patriots Path, Mendham, NJ 07945, USA"
},
 
・
・
・

]
```

>route_guide_db.jsonは言語依存のないデータファイル。ここでは、たまたまpython配下に配置している。


<br>
##8. メソッドの実行
クライアントファイルを実行する。

現在のターミナルはサーバー起動中のため、別のターミナルから実行。

```ruby:
$ bundle exec route_guide/route_guide_client.rb ../python/route_guide/route_guide_db.json
``` 


**▼実行結果**

```
省略
"RecordRoute"
"-----------"
"- next point is <Routeguide::Point: latitude: 412856162, longitude: -745148837>"
"- next point is <Routeguide::Point: latitude: 418858923, longitude: -746156790>"
"summary: <Routeguide::RouteSummary: point_count: 10, feature_count: 5, distance: 9813674, elapsed_time: 8>"
```

実行結果として最終的にsummaryが返り完了。


<br>
##参考リンク

- [gRPCの概要](https://grpc.io/docs/what-is-grpc/introduction/)
- [gRPC rubyのチュートリアル](https://grpc.io/docs/languages/ruby/basics/)
- [github](https://github.com/grpc/grpc/tree/v1.34.0/examples/ruby/route_guide)
- [protocol buffers（proto3）](https://developers.google.com/protocol-buffers/docs/proto3)