【GoとgRPCとバリデーション】go-proto-validatorsでgrpcurlを使えるようにする

# はじめに
gRPCサーバのバリデーションを実装するにあたって、[go-proto-validators](https://github.com/mwitkow/go-proto-validators)は大変便利なのですが、gRPCクライアントツールで動作確認できないという罠に嵌ったので、原因と解決策を共有します。

# go-proto-validatorsの基本
## go-proto-validatorsとは？
`go-proto-validators`はGo向けのgRPCのバリデーションツールです。
protoファイルに直接バリデーションルールを記載することでIF情報を一元管理できるようになるのと、バリデーションコードの自動生成ができるのが良いところです。

使い方やルール一覧などの詳細はGitHubのページを確認してください。
Star数の割にはかなり良さそうです。

## なぜgo-proto-validatorsを使いたいのか？
gRPCサーバのバリデーション実装には、REST-APIサーバ同様に[ozzo-validator](https://github.com/go-ozzo/ozzo-validation)を使っていたのですが、下記の課題がありました。

- バリデーションを自分で実装するのが面倒くさい
- IF仕様書にバリデーション情報をかけない（コメントで書くことはできるけどダサい）
- コメントだとダサいから別ファイル（Markdown）でバリデーション仕様管理してたけどとてもイケてなかった

ちなみに、`ozzo-validation`は良いライブラリですし、別システムでも使っていますし、[紹介記事](https://qiita.com/gold-kou/items/201a19d9d0c760cc2104)も書いていますので、非難するつもりはありません。

また、「わざわざgRPCじゃなくてこれまで通りRESTで充分なんじゃないの？」という声に対しても、バリデーション実装の自動生成を一つの理由にあげることができます。(小さい理由ですが)
なぜかというと、私の所属するチームでは、REST-APIサーバを実装する際には`openapi-generator`を利用しているのですが、スキーマが自動生成時に上書きされてしまうので、バリデーション実装を自動生成してくれる`go-playground/validator`を使っておらず、`ozzo-validation`を使って自前で実装しているからです。（それでもOpenAPIのコード自動生成の恩恵の方が大きいという判断）

# 問題発生！gRPCクライアントツールでリクエストできない
grpc_cli, evans, grpcurlなどのgRPCクライアントツールは、`go-proto-validators`を利用したサーバに正常にリクエストができません。下は実際に実行した例です。

```console:grpc_cliの失敗例
$ grpc_cli call localhost:50051 YourService 'key1: "value1"' --metadata="authorization:api_key"
[libprotobuf ERROR google/protobuf/descriptor.cc:3587] Invalid proto descriptor for file "products.proto":
[libprotobuf ERROR google/protobuf/descriptor.cc:3590]   github.com/mwitkow/go-proto-validators/validator.proto: Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors.
Method name not found
Failed to find method YourService in proto files.
Method name not found
Method name not found
Failed to parse request.
```

```console:grpcurlの失敗例
$ grpcurl -plaintext -d '{"key1": "value1"}' -H "authorization:api_key" localhost:50051 YourService
Error invoking method "YourService": target server does not expose service "YourService"
```

原因は`go-proto-validators`が依存している`gogo/protobuf`が[Server Reflection](https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md)に対応していないことです。
https://github.com/mwitkow/go-proto-validators/issues/34#issuecomment-403082678

Server Reflectionは、gRPCサーバの情報提供や通信接続をダイレクトに利用できるようにしてくれる機能です。gRPCクライアントツールは基本的にこのServer Reflectionを使っています。

本件についてはissueが現在(2019/11)もgrpc上で上がっています。
https://github.com/grpc/grpc-go/issues/1873

# 解決方法
grpcurlであれば以下の2Stepsで実行が成功するようになりました！

## Step1. import-pathとprotoオプションを指定する
```console:コマンド例
$ grpcurl -insecure -import-path your_service_directory -proto your_service.proto -d '{"key1": "value1"}' -H "authorization:api_key" localhost:50051 YourService
```

>   -import-path value
    	The path to a directory from which proto sources can be imported, for use with -proto flags.
    	
>   -proto value
    	The name of a proto source file. Source files given will be used to determine the RPC schema instead of querying for it from the remote server via the gRPC reflection API.

とあるように、`import-path`オプションは`proto`オプションとセットで使うようです。
`proto`オプションを指定することでServer Reflectionを使用しないようにできます。
your_service_directoryには、protoファイルが配置されているディレクトリパスを指定してます。

## Step2. validator.protoファイルをローカルに配置する
`go-proto-validators`の[validator.protoファイル](https://github.com/mwitkow/go-proto-validators/blob/master/validator.proto)をローカルに配置します。
validator.protoはgo getで取得したものをコピペしてもいいですし、githubからダウンロードして取得しても良いです。
リモートだとうまくいかなケースがあったため、このような対応をしています。

配置場所は上記の`import-path`オプションで指定したディレクトリ配下で`github.com/mwitkow/go-proto-validators/validator.proto`という具合にします。

これで、grpcurlでリクエストできるようになりました。
やったね！

## 注意点
この解決策には問題があります。
それは、大元のgo-proto-validators/validator.protoの変更に追従できないことです。
ただ、このファイルの変更はあまりなさそうなのと、git submodleでの対応も可能だと思われます。

# 最後に
gRPCは最初は予想外のところでつまずくことが多いですが、やってみると楽しいです。

結局、Team Leaderのおかげで自己解決できましたが、Gophersスラックにデビューできたので、詰まって良かったです。
https://gophers.slack.com/archives/C03RB2KAD/p1573531143286700