gRPCでサーバー・クライアントを繋げてみる(iOS編)

[Aizu Advent Calendar](https://adventar.org/calendars/3938) の22日目の記事です。
大遅刻しました。申し訳ないです。。

gRPCでサーバー・クライアントを繋げてみたもののiOS編です。

## gRPCとは
[こちらの記事](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6)でわかりやすく簡単に紹介されています。

gRPC自体は `.proto` ファイルを使用し、それを元に生成したコードを使用してクライアントから簡単にサーバー側のデータを取得することができるものです。
上記の点から、特定のエンドポイントに対して必要な値を載せてリクエストを飛ばして○○というデータが返ってくるというAPI定義書を作成することなく使用する側はサーバーからデータを取得することができます。

また、HTTP/2を使用、データ取得関数使用時にシリアライズを行うため、高速に通信することができます。

## 実装編
### `proto` ファイルを定義する
クライアントとサーバーにとっての共通のインターフェースである `proto` ファイルを定義します。

```proto
syntax = 'proto3';

package greeter;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```

この `proto` ファイルの内容自体は [gRPCのガイド](https://grpc.io/docs/guides/)にあるものにいくつか追加して書き入れているものがあります。
以下の２つです。
#### `syntax = 'proto3'`
使用するプロトコルバッファーのバージョンを指定しています。指定していない場合、`proto` ファイルからソースコードを生成する際にエラーが吐かれます。
#### `package`
この追加している部分はクライアント側ではあまり意味がありません。サーバー側での話になるので割愛します。

また、この `proto` ファイルの内容自体はすごく簡単なものとなっていて、`message HelloRequest` で指定した値を使用してリクエストを送ると `message HelloReply` で指定した値が返ってくるようになっているというものです。
この流れを `service Greeter` で定義しています。

今回この `proto` ファイルはクライアント側で定義して持つのではなく、サーバー側で持ち、サーバー側のリポジトリをサブモジュールとして登録して `proto` ファイルを参照するようにしてみています。

### ソースコードを生成する
今回コマンドラインで `proto` ファイルからソースコードを生成するようにしているのですが、少しばかり長いコマンドとなっています。
それを毎回簡単に叩くために今回は Makefile を作成しました。
また、 `proto` ファイルから生成する際に `protoc-gen-grpc-swift` というコマンドと `protoc-gen-swift` というコマンドが必要なので、今回は先ほどの `proto` ファイルと同じく [grpc-swiftリポジトリ]()を　サブモジュールとして登録し、そのディレクトリへパスを Makefile 内で通してコマンドを使用できるようにしました。

 ```make
export PATH += :$(PWD)/grpc-swift
hello:
	protoc hello.proto\
	    --proto_path=gPRC-server-java/server/src/main/proto/ \
	    --grpc-swift_out=./gRPC-client\
	    --swift_out=./gRPC-client
```

#### :warning: 注意点 :warning:
**忘れてはいけないのが、ソースコードを生成した後にXcode内で生成したファイルを参照することです。
参照しなくてはビルド時にコンパイルの対象とならないため、エラーが発生してしまいます。**

##### やり方
追加対象のGroupを右クリック > Add Files to "追加Group名"... > 生成されたファイル2つを選択 > Add

#### `protoc` コマンドについて
`protoc` コマンド自体はこういった形式で使用します。

```sh
protoc protoファイル名 ...各種オプション
```

`protoc` コマンドをインストールする方法については、MacOS環境であれば 

```shell
brew install protobuf
``` 
とすればコマンドが入ってくれます。

#### オプション類について
[`protoc` コマンドについて]() であった "各種オプション"について説明します。
##### `--proto_path`
指定した protoファイル名 のprotoファイルが存在するディレクトリを指定します。
今回の例の場合、以下のようなファイルツリーのため、`gPRC-server-java/server/src/main/proto/` を指定します。

```
gPRC-server-java
├── db
└── server
    ├── build
    ├── gradle
    │   └── wrapper
    └── src
        ├── main
        │   ├── java
        │   ├── proto
        │   │   ├── hello.proto
        │   └── resources
        └── test
```

##### `--grpc-swift_out`
`protoファイル名.grpc.swift` ファイルの出力先を指定するオプションです。
このファイルがないと `Service` が使用できず、サーバー側からデータが取得できません。
今回は `ViewController.swift` などと同じ階層に出力されるようにしています。
このオプションがない場合、単純にモデル定義をしているだけのため、コンパイルエラーとなったりはしません。

##### `--swift_out`
`protoファイル名.pb.swift` ファイルの出力先を指定するオプションです。
このファイルがないと `proto` ファイル内で定義した `message` が使用できません。
また、 `--grpc-swift_out` のみ指定していた場合に `protoファイル名.grpc.swift` ファイル内でコンパイルエラーとなります。

### gRPCのswiftライブラリをプロジェクトの依存関係に追加する
今回は Swift Package Manager を使用してライブラリをプロジェクトに追加します。
Xcode11 以上が前提の内容となっていますので、10.x以下の方はアップデートするか、Cocoapodsを使用するなどしてください。

トップバーにある `File` > `Swift Packages` > `Add Package Dependency...` の順にクリックすると、以下のような画面が出てきます。
<img width="729" alt="スクリーンショット 2019-12-27 21.59.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180744/886c256d-e38b-965c-5459-b8281d6ac2d8.png">

この画面の `Enter package repository URL` と書いてある場所にgRPCのSwiftライブラリである `grpc-swift` のリポジトリのURL (https://github.com/grpc/grpc-swift) を入力します。
すると、リポジトリのURLがラベルとして埋め込まれ、ルールを選ぶことのできる画面に進むと思います。
ほとんどの場合はそのまま `Next` を選択しても大丈夫なのですが、今回は公式のREADMEで `master`    ブランチは非推奨になっていると書いてあり、 `nio`  ブランチを使用することが推奨されていたので `nio` ブランチを指定します。
`Branch` を選択し、入力欄に `nio` と入力して `Next` をクリックします。
最後にそのまま `Finish` をクリックして依存関係の追加は完了です。

### リクエストを送ってレスポンスを表示してみる

完成形

```swift:ViewController.swift
import UIKit
import GRPC
import NIO

class ViewController: UIViewController {

    let client: Greeter_GreeterServiceClient = {
        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        let configuration = ClientConnection.Configuration(
            target: .hostAndPort("localhost", 6565),
            eventLoopGroup: group)
        let connection = ClientConnection(configuration: configuration)
        return Greeter_GreeterServiceClient(connection: connection)
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let request = Greeter_HelloRequest.with {
            $0.name = ""
        }
        do {
            let response = try client.sayHello(request).response.wait()
            print(response)
        } catch let e {
            print(e.localizedDescription)
        }
    }
}
```
これだけだと味気ないので実装の手順を説明を交えながらしていきます。

#### 1. clientを作成する
```swift
let client: Greeter_GreeterServiceClient = {
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    let configuration = ClientConnection.Configuration(
        target: .hostAndPort("localhost", 6565),
        eventLoopGroup: group)
    let connection = ClientConnection(configuration: configuration)
    return Greeter_GreeterServiceClient(connection: connection)
}()
```
この部分です。

リクエストを送るための関数が定義されているクラス(※1)を初期化するために `connection` を引数として渡す必要があるため、定義します。

今回、connectionを生成するにあたっての値として、
- host が `localhost`, portが6565(gRPCサーバーのデフォルトポート)
- スレッド数が1の `EventLoopGroup` (※2)
を指定します。

※1 ファイル内で定義したpackage名・service名が使用されたクラスは `package名_service名Client` という形式の名前で生成されています。package が指定されていなかった場合 `service名_Client` という名前になっています。
※2 `EventLoopGroup` については、 [swift-nio](https://github.com/apple/swift-nio) というライブラリで定義されている protocol で、イベントストリームについて扱うものとなっています。こちらについてはあまり深く調べられていないので割愛します。

#### 2. リクエストの `message` を作成する
```swift
let request = Greeter_HelloRequest.with {
    $0.name = ""
}
```
リクエストの `message` にはファクトリメソッドが存在していて、一回イニシャライズして値を入れていくということをする必要がなく、便利なのでそれを使用します。
今回用意するサーバーでは `name` の値に関わらず `Hello` を返すようにしているので空文字を入れています。

#### 3. リクエストを送り、レスポンスを標準出力に表示する
今回のアプリでは、アプリ自体の見た目には表示しません。（手抜きです。すみません。）
アプリ自体の見た目に表示すること自体は調べたらいくつも出てきたりなどしますので、そちらを参照してください。

```swift
do {
    let response = try client.sayHello(request).response.wait()
    print(response)
} catch let e {
    print(e.localizedDescription)
}
```
先ほど作成した `client` を使用し、リクエストを送信します。
`client` には `proto` ファイル内の `service` で定義した `rpc` の関数（以後リクエストの関数）が定義されていて、それを使用するとリクエストを送ることができます。
今回の例だと、`sayHello(_ HelloReply)` が定義されています。

リクエストの関数の返り値の中に `response` というプロパティがあり、このプロパティ自体はまた先程も出てきたのですが、[swift-nio](https://github.com/apple/swift-nio) というライブラリで定義されている `EventLoopFuture` という型です。
このプロパティをそのまま使用しようとしても値が取り出せないので、取り出すために `wait` 関数を使用します。また、この関数は `throws` で定義されているため、`do ~ catch` で囲み、`try` をつけて実行します。
最後に、`wait` 関数の返り値をそのまま出力して、完了です。

```shell:コンソール
2019-12-27T22:31:33+0900 info: gRPC connection to [IPv6]::1/::1:6565 on SelectableEventLoop { selector = Selector { descriptor = 4 }, thread = NIOThread(name = NIO-ELT-0-#0) } ready
gRPC_client.Greeter_HelloReply:
message: "Hello"
```

## 感想など
実装自体とても簡単で、共通のインターフェースを提供して型安全に通信ができるのはすごくいいなと感じました。
予定ではもっと前に投稿しようかと思っていたのですが、gRPCとは関係のないところでサーバー側でうまくいかない箇所があり、このタイミングでの投稿となりました。
サーバー側については書いていませんが、記事に書いていることをそのままやってみると HelloWorld はできるようにしたつもりです。試してみていただけるとありがたいです。

今回は試せてないですが、

- Date型など複数のフォーマットのあるものはどのように扱うのか？
- クライアントのコードだけをprotocコマンドで生成することは可能なのか？
- 認証周りについて

上記三点について、今後調べつつ試していきたいと思っています。