はじめに
gRPCサーバのバリデーションを実装するにあたって、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を使っていたのですが、下記の課題がありました。
- バリデーションを自分で実装するのが面倒くさい
- IF仕様書にバリデーション情報をかけない(コメントで書くことはできるけどダサい)
- コメントだとダサいから別ファイル(Markdown)でバリデーション仕様管理してたけどとてもイケてなかった
ちなみに、ozzo-validation
は良いライブラリですし、別システムでも使っていますし、紹介記事も書いていますので、非難するつもりはありません。
また、「わざわざgRPCじゃなくてこれまで通りRESTで充分なんじゃないの?」という声に対しても、バリデーション実装の自動生成を一つの理由にあげることができます。(小さい理由ですが)
なぜかというと、私の所属するチームでは、REST-APIサーバを実装する際にはopenapi-generator
を利用しているのですが、スキーマが自動生成時に上書きされてしまうので、バリデーション実装を自動生成してくれるgo-playground/validator
を使っておらず、ozzo-validation
を使って自前で実装しているからです。(それでもOpenAPIのコード自動生成の恩恵の方が大きいという判断)
問題発生!gRPCクライアントツールでリクエストできない
grpc_cli, evans, grpcurlなどのgRPCクライアントツールは、go-proto-validators
を利用したサーバに正常にリクエストができません。下は実際に実行した例です。
$ 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.
$ 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/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オプションを指定する
$ 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ファイルをローカルに配置します。
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