gRPC on Spring Boot with grpc-spring-boot-starter

この記事は[BrainPad Advent Calendar 2018](https://qiita.com/advent-calendar/2018/brainpad) 3日目の記事になります。

こんにちは[@nissy0409240](https://twitter.com/nissy0409240)です。
BrainPadでエンジニアをしています。
2018年もあと一ヶ月を切りましたが、
皆様いかがお過ごしでしょうか。

私事ではございますが
今年は[PyCon2018で登壇させて頂いた](http://nissy0409.hatenablog.com/entry/2018/09/24/035332)り
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100333/98f28040-ec6f-3555-d88c-69af15d87b76.png)
W杯でC・ロナウドのハットトリックを現地で観戦するなど
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100333/9b50b91c-8ab7-c260-6dc6-c85e86c8b6e7.png)
公私に渡り貴重な経験をさせて頂きました。

そんな2018年の最後に、上で書いた内容と全く関係無いgRPCをSpring Bootで使うにあたり、とんでもなくはまった箇所と(それだけでは文章量が少なすぎるので)gRPCってそもそも何かを添えて、本エントリーをお送りさせて頂きます。

# gRPCとは

まず、gRPCとは何かについて説明します。
gRPCとはGoogleが作成したRPCフレームワークです。
これだけでは「なんのこっちゃ」な状態なのでRPCフレームワークについても説明します。

RPCフレームワークのRPCの正式名称は"Remote Procedure Call"と言います。
翻訳すると「遠隔手続き呼び出し」となります。
違うサーバ上で動いているアプリケーションに記載されているメソッドを手元のサーバで動いているものであるかのように呼び出せるようにするものです。

文字だけではイメージつきにくいので、gPRC本家のドキュメントに記載されている図と一緒に見ていきましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100333/43de6f9c-5916-4d6e-5741-d80c4f36c5c3.png)

上の図ではC++で作られたサービスをRubyのクライアントとJavaのクライアントが呼び出している状況を示しています。

# gRPCを使うメリット

gRPCではサービス側が提供したい実装内容を.protoファイルという定義ファイルに記述することで、クライアント側からサービスの実装を呼び出すスタブという名前の連携モジュールを自動生成してくれます。
これにより、API連携で見られるインターフェース仕様定義ファイルが実装に必ず反映されるため、以下のような利点を享受することが出来ます。

- 呼び出し元・呼び出し先の双方でどのような型のデータをやりとりするかを共有することが出来る。
- http2 + protocol buffersを用いているためAPI連携よりパフォーマンスを向上させることも可能
- 呼び出し元・呼び出し先で使用されている言語に関係無くマイクロサービス化を進めていくことも出来る
    - gRPCでは様々な言語に対応しており、Web開発で用いられている主要な言語はほぼほぼ対応済
<img width="806" alt="スクリーンショット 2018-12-03 1.57.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/100333/f2321265-c8f1-4660-b2cc-081983e9bcb6.png">

# gRPCの使い方

## pom.xmlへのモジュール追加

gRPCの使い方についてご紹介させて頂きます。
とはいえ、文字だけでは伝わりづらいですし、各言語ごとに異なるものですので、
今回はJava8 + Spring Bootを使い、実際に手を動かしながら進めていこうと思います。

まず、雛形となるプロダクトを[Spring Initializer](https://start.spring.io/)を使って作成します。
作成前の雛形の構成はこちらです。

```shell:demo_protobuf
├── pom.xml
├── main
│   ├── java
│   │   └── com
│   │       └── example
│   │           └── demo_protobuf
│   │               └── DemoProtobufApplication.java
│   └── resources
│       └── application.properties
└── test
    └── java
        └── com
            └── example
                └── demo_protobuf
                    └── DemoProtobufApplicationTests.java
```

続いてpom.xmlにモジュールを追加しますが、ここがとんでもなくはまった箇所でした。
モジュールの追加については後述するとして、先に定義ファイルについての説明に移ります。

## 定義ファイルの作成方法

実行が完了したら定義ファイルを作成します。
今回は`src/main/proto/demo.proto`ファイルに定義を書いていきます。
この時点でのプロダクトの構成は以下の通りとなります。

```shell:demo_protobuf
.
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo_protobuf
    │   │               └── DemoProtobufApplication.java
    │   │           
    │   ├── proto
    │   │   └── demo.proto
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── example
                    └── demo_protobuf
                        └── DemoProtobufApplicationTests.java
```

作成したdemo.protoには以下のように記述していきます。

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

option java_multiple_files = true;
option java_package = "com.example.fizzbuzz";
option java_outer_classname = "FizzBuzzProto";

package calc;

// Mathというサービスの戻り値と引数の組み合わせを定義します
service Math {
    rpc Math (MathRequest) returns (IntResponse);
}

// IntResponseを選択した時の戻り値の型と個数を定義します
message IntResponse {
    int32 value = 1;
}

// MathRequestを定義した時の引数の型と個数を定義します
message MathRequest {
    int32 x = 1;
    int32 y = 2;
}
```

ピンときた方もいらっしゃると思いますが、.protoファイルには
「どのサービスがどのようなリクエストを受け、どのような戻り値を返すか」が書かれています。
まさに.protoファイルがどのような型のデータをやりとりするかが書かれているのです。

記述後は`mvn clean install`を実行します。
実行後、ソースコードが以下のように生成されます。
(実際にはもっと沢山のコードが生成されますがこの場では省略させて頂きます)

```
.
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── demo_protobuf
│   │   │               └── DemoProtobufApplication.java
│   │   ├── proto
│   │   │   └── demo.proto
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── demo_protobuf
│                       └── DemoProtobufApplicationTests.java
└── target
    └── generated-sources
        ├── annotations
        └── protobuf
            ├── grpc-java
            │   └── com
            │       └── example
            │           └── fizzbuzz
            │               └── MathGrpc.java
            └── java
                └── com
                    └── example
                        └── fizzbuzz
                            ├── FizzBuzzProto.java
                            ├── IntResponse.java
                            ├── IntResponseOrBuilder.java
                            ├── MathRequest.java
                            └── MathRequestOrBuilder.java
```

## コードの再生成

しかし、ここで私はミスをしてしまったことに気付きました。
最初はFizzBuzzにしようとしたのを止めてMathに変更したのにも関わらず、
一部FizzBuzzのままコードを生成してしまったのです。
このままリポジトリを破棄して作り直しでしょうか。
そんなことはありません。

作成したdemo.protoを以下のように書き換え

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

option java_multiple_files = true;
option java_package = "com.example.math";
option java_outer_classname = "MathProto";

package calc;

// Mathというサービスの戻り値と引数の組み合わせを定義します
service Math {
    rpc Math (MathRequest) returns (IntResponse);
}

// IntResponseを選択した時の戻り値の型と個数を定義します
message IntResponse {
    int32 value = 1;
}

// MathRequestを定義した時の引数の型と個数を定義します
message MathRequest {
    int32 x = 1;
    int32 y = 2;
}
```
`mvn clean install`を実行しましょう。

```
.
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── demo_protobuf
│   │   │               └── DemoProtobufApplication.java
│   │   ├── proto
│   │   │   └── demo.proto
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── demo_protobuf
│                       └── DemoProtobufApplicationTests.java
└── target
    └── generated-sources
        ├── annotations
        └── protobuf
            ├── grpc-java
            │   └── com
            │       └── example
            │           └── math
            │               └── MathGrpc.java
            └── java
                └── com
                    └── example
                        └── math
                            ├── MathProto.java
                            ├── IntResponse.java
                            ├── IntResponseOrBuilder.java
                            ├── MathRequest.java
                            └── MathRequestOrBuilder.java
```

target下のコードが作り直されています。
また、demo.protoを更新したい時はこの方法で良いこともわかりますね。

後は生成されたコードを継承してサービスとクライアントを作成すれば完成です。

## pom.xmlにモジュール追加時にはまったこと

一連の流れについて触れましたのでここではまったことについてご紹介させて頂きます。
端的に言ってしまうと「モジュールのgroupIdがMaven Repositoryで出てきた内容と異なるので注意が必要」ということでした。

そもそもMaven Repositoryとは何か。ご存知の方も多いと思われますが、ミドルウェアをインポートする際にどのような記述をするかを教えてくれるなくてはならないサイトです。
他言語の例で言えば、PythonのPyPiが近いものにあたります。

そして、Spring Bootというフレームワークに対応した全部入りツールいわゆるスターターと呼ばれるものも数多く存在しています。
例えば、認証モジュールを作る際には[Spring Boot Security Starter](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security)を使うといった具合です。

いままで、Spring BootでgRPCを使う際には以下のような書き方をしていました。

```java:pom.xml
<!-- https://mvnrepository.com/artifact/org.lognet/grpc-spring-boot-starter -->
<dependency>
    <groupId>org.lognet</groupId>
    <artifactId>grpc-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>
```

しかし、このモジュールの最新バージョンが3系へアップデートされた際、groupIdが"org.lognet"から"org.github.lognet"へと変わってしまったため、バージョンだけでなくgroupIdも変えなくてはならなくなりました。

[grpc-spring-boot-starterのリポジトリ](https://github.com/LogNet/grpc-spring-boot-starter#2-setup)にも上記と同様の情報が書かれています。
<img width="1145" alt="スクリーンショット 2018-12-04 2.25.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/100333/2e485cce-2602-3a40-e2b3-dfb2cd7b57b8.png">


そのため、今後は以下の通りに記述する必要があります。

```java:pomxml
<dependency>
    <groupId>io.github.lognet</groupId>
    <artifactId>grpc-spring-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
```

こちらgroupIdを未修正のままですと`mvn install`時には下記のようなエラーが出てしまうので注意が必要です。

```
[ERROR] Failed to execute goal on project demo_protobuf: Could not resolve dependencies for project com.example:demo_protobuf:jar:1.16.1: Failure to find org.lognet:grpc-spring-boot-starter:jar:3.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException
```

# 結び

構成としては少しおかしくはなりましたが日本語のエントリーが少ない情報をエラーメッセージとともに掲載することでハマっている方の助けになればと考え、本エントリーの結びと代えさせて頂きます。
まとまりのない文章となってしまい、申し訳ございませんでしたが最後までお付き合い頂きありがとうございました。

## 参考

https://qiita.com/nozaq/items/9cd9bf7ee6118779bda9
http://redj.hatenablog.com/entry/2017/10/13/084423
https://github.com/LogNet/grpc-spring-boot-starter
https://kengotoda.gitbooks.io/what-is-maven/primer/maven-repository.html