golang ✕ gRPCのprotoでルールベースValidateを行う [gRPC ✕ golangをDRYに Part.3 Final]

## 読者ターゲット
- [株式会社viviON](https://vivion.jp/)にちょっと興味ある
- golang ✕ gRPCで毎回Validateのロジック書いてる方
- gRPCちょっとわかるよって方
- protoちょっとわかるよって方

## 課題感

### 毎回golangのvalidateロジックを書くの、疲れませんか？

golangのValidationといえば[Package validator](https://github.com/go-playground/validator)だと思います。
(v10まで出てますし、老舗感ありますよね。)

gRPCのRPCに対して毎回個別のvalidator structを用意して、開発するのって結構しんどいんですよね。。。

jsonのお取り扱いだったら[zod](https://zod.dev/)も似たように先にvalidator objを作って検証するんですけど、json書くのとstruct書くのって全然工数違ってくるんですよね。。。 (疲弊感)


## 解決しよう

折角protoファイル書いてるんだからスキーマ定義段階で事前にスキーマにカスタムパラメータを付与することで、回避できないか？
と考え始めました。

同じことを考えた先人たちは素晴らしいことに、既に用意してくれていました。
そのOSSは[PGV (protoc-gen-validate)](https://github.com/bufbuild/protoc-gen-validate)です。
(※Buf Technologies, Inc.の製品ばかりご紹介していますが回し者ではないです。)


### 例えば特定のstring型のスキーマを必須項目にしたい場合
```min_len = 1```とすることにより、「最低1文字以上の文字列を入力する必要がある」となります。
その為、擬似的に```required = true```とすることが可能になります。

```proto
syntax = "proto3";

package greet.v1;

option go_package = "example/gen/greet/v1;greetv1";

import "validate/validate.proto"; // ココ重要　☆

message GreetRequest {
  string name = 1 [(validate.rules).string.min_len = 1]; // ココ重要 ☆
}

message GreetResponse {
  string greeting = 1;
}

service GreetService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}
```

## packageをprotoに認識させる
公式Docにも書いてるんですが、若干わかりにくいので書いておきます。

### Install

pathがenvoyproxyになっているのはまだ、go側がmodule pathの変更に時間がかかってるそうです ><
(※新しくpathを切っただけだと、旧と新の2つのmoduleが存在してしまう為だと思います。)
```sh
go get -d github.com/envoyproxy/protoc-gen-validate
```

### Buf config

※ Bufについて知りたい方は[こちら!!](https://qiita.com/taisei_otsuka/private/78d49f03536086944d07)

```yaml:buf.yaml
version: v1
deps:
  - buf.build/envoyproxy/protoc-gen-validate ## ココ重要 ☆
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT
```

```yaml:buf.gen.yaml
version: v1
plugins:
  - plugin: go
    out: gen
    opt: paths=source_relative
  - plugin: connect-go ## connect-goでも通常のgRPCでも実現できるのでconnect-goを使わない場合は不要です
    out: gen
    opt: paths=source_relative
  - plugin: buf.build/bufbuild/validate-go ## ココ重要 ☆
    out: gen
    opt: paths=source_relative
```

```buf generate```コマンドでvalidateメソッドが実装されたRPCのリクエスト structが生成されます。

## ValidationをDRYにする
生成されたprotobufには既に```.Validate()```メソッドが実装されたリクエスト structが生成されているので、各RPCで以下を書いてもいいのですが、記述量が少なくなったとはいえ毎回ロジックを書かなければいけない世界線からは開放されません。
```golang
if err := req.Validate(); err != nil {
```

## 毎回コードを書かずに自動Validationを行う方法

gRPCオフィシャルの[Validator module](https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware/validator)を使用することで、リクエスト structに実装されている```.Validate()```関数の存在チェックを行い勝手にCallしてくれます。

Interceptorで実装されている為、各RPCで記述しなくてもすべてのRPC Callに対して自動的にValidateをかけてくれるようになります。

※ [connect-go](https://github.com/bufbuild/connect-go)でも独自に[こんな風](https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a18e1e2bacb23afca0f52b228f6b4efbb5f57822/interceptors/validator/validator.go#L30)にカスタムInterceptorを実装することで、自動Validateが可能になります。

## まとめ
第三部作で書いてきた「gRPCを取り巻く環境はどんどん進化していた」ですが、如何でしたでしょうか？

2,3年前にはなかった「これが欲しかった」がどんどん増えてきており、コミッター達には感謝してもしきれないです。

## 採用PR

https://hrmos.co/pages/vivion/jobs/0000226

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307359/2000f749-3e82-7a47-b1c4-4444e237f79c.png" width=45%>