更新
この記事でやりたいけどできないと言っていたことができるようになりました。
https://github.com/LogNet/grpc-spring-boot-starter#5-implementing-message-validation
記事を投稿してわずか一週間の出来事でしたね。。いや、ありがたいです、ええ。
はじめに
REST通信しか知らなかった私が最近出会ったgRPCで、ちょっと困ったことがありました。
それは誰もがやりたい入力値チェックです。
gRPCだから?Kotlinだから?そんな難しいことはないでしょう…
と思って調べ終わったらもう冬でした。
なんて書いていたらちょうど今職場でみかんを頂いたので載せておきます。
こんなんなんぼあってもいいですからね
コード(今回の変更差分)
個人リポジトリで技術検証をしているので、そこで本記事の内容を検証した際の差分が以下となります。
-
クライアントサイド
https://github.com/ymtk172/ytmp_user-api/commit/0808880716aedb33080f47dc9e62864bcf23fd47 -
サーバサイド
https://github.com/ymtk172/ytmp_thermo-monitor/commit/e9080aa69b7bcaa83c1c83d415890922e23687a3
後半で説明していきます。
余談(経緯)
紆余曲折、苦難の連続
何も始めからBean Validationじゃないと入力値チェックしたくない!というわけではありませんでした。
gRPCは型ありきのプロトコルです。その型に入る値のチェックくらい…
…
できない…
寄り道したところ
OSS
- protoc-gen-validate
- go-proto-validators(Golangですが紹介だけ)
誰もが辿り着く場所です。個人プロダクトなら十分だと思います。ただ、業務用途なので、
- product自体が信用できる品質か
- 正しく動作するか
- 性能に影響を与えないか
- セキュリティが考慮されているか
- ownerが信用できるか
- 急に無茶な変更をしない
- 依存ライブラリを含めてセキュリティアップデートをしてくれるか
- 中長期でgRPCのバージョンアップをサポートするか
これを証明しないと自信を持って「使います!」と言えない。。
(ownerの関係者が知り合いとかであれば、そこらへん聞けるんですけどね。)
ちなみに1つ目のprotoc-gen-validateですが、README.mdの1行目を要チェックです。(2020/12/4時点)
This project is currently in alpha. The API should be considered unstable and likely to change
(Google翻訳)このプロジェクトは現在アルファ版です。 APIは不安定であり、変更される可能性があると見なす必要があります
…一層使うと言える自信が持てなくなりました。
Custom Optionsを使用した自前実装
自前でprotoファイル上にバリデーション定義を設けることもできるようです。
gRPCの設計・開発の勘所をまとめてみた( @yatarou )
選ばれたのはBean Validationでした
やはり使い慣れたものはいいですね。
有名だし、簡単に実装でき…
…
簡単じゃない…??
何ができない?
gRPCで自動生成したJavaコードに、アノテーションが付与できない。
継承できない…
アノテーションはどこに…
まだ諦めない
Bean ValidationにはXMLでの定義もできるらしい!
業務コードから呼び出そう!
公式Doc
https://beanvalidation.org/2.0-jsr380/spec/#xml
ちょっと……情報少ないですね。。
Google先生に聞いても教えてくれませんでした…
ですが、"頼れる先輩"の協力も得て頑張って実装することができました。
実装方法
上に載せたCommit差分を説明していきます。
サーバサイド
バリデーション定義(XML)
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration validation-configuration-2.0.xsd"
version="2.0">
<constraint-mapping>META-INF/UserInfo.xml</constraint-mapping>
</validation-config>
Validation定義を書いたXMLが増えたらここに追記していきます。
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mappings
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd"
version="2.0">
<default-package>com.yamalc.ytmp.grpc.user</default-package>
<bean class="UserInfo" ignore-annotations="false">
<getter name="id">
<constraint annotation="javax.validation.constraints.Size">
<element name="min">2</element>
<element name="max">50</element>
</constraint>
</getter>
<getter name="displayName">
<constraint annotation="javax.validation.constraints.Size">
<element name="min">8</element>
<element name="max">50</element>
</constraint>
</getter>
</bean>
</constraint-mappings>
(私はバリデーションエラー時のメッセージをカスタマイズしたいという欲は持っていないので定義していませんが、 <message>
を定義する際は <element>
より上に書かないと怒られるようでした。)
呼び出し方法(Kotlin)
...
class UserServiceImpl(private val usersMapper: UsersMapper, val validator: Validator) : UserGrpc.UserImplBase() {
...
//Validator#validateでXMLのバリデーション定義を実行
val constraintViolations: Set<ConstraintViolation<UserInfo>> = validator.validate<UserInfo>(userInfo)
//SetがEmptyでなければ、バリデーションエラー有り
if(constraintViolations.isNotEmpty()) {
//エラー時の処理
}
...
無事、バリデーションすることができました。
エラーハンドリングはお好みに合わせて実装して頂ければと思います。(決して力尽きたわけでは…)