govalid でドメインバリデーションのコード生成を導入した
に公開
に公開
個人開発の Go バックエンドで、ドメイン層のバリデーションに govalid を導入しました。
govalid は構造体フィールドにマーカーコメントを書くだけで、型安全なバリデーションコードを自動生成してくれるツールです。リフレクション不使用でゼロアロケーション、go-playground/validator と比べて 5〜44 倍高速とのこと。早すぎる。
勤務先では go-playground/validator を使っていたのですが、個人開発ではzennの記事をみて気になっていた&リフレクションなしで動くものを試してみたくて govalid を選びました。
この記事では、govalid の基本的な使い方と、導入時にハマったポイントをまとめていきます。
Go 1.24 以降なら go get -tool で go.mod に tool ディレクティブとして追加できます。
go get -tool github.com/sivchari/govalid/cmd/[email protected]go.mod に以下が追加されます。
tool github.com/sivchari/govalid/cmd/govalidチーム全員が同じバージョンを使えるし、go install のようにグローバルにインストールする必要もないので、こちらのほうが取り回しが良い気がしております。
go tool govalid ./...マーカーコメントが付いた構造体に対して {filename}_{struct}_validator.go というファイルが生成されます。
基本的な使い方はgithubを見るとわかると思うので、ここでは簡単な例を交えて説明します。
構造体のフィールドの直前に //govalid:<マーカー> の形式でコメントを書きます。
type User struct {
//govalid:required
//govalid:maxlength=100
Name string
//govalid:required
//govalid:email
Email string
//govalid:gte=0
//govalid:lte=120
Age int
}主要なマーカーはこのあたりです。
| カテゴリ | マーカー | 説明 |
|---|---|---|
| 存在チェック | required | ゼロ値でないこと |
| 数値比較 | gt / gte / lt / lte | 大小比較 |
| 文字列長 | minlength / maxlength | Unicode 対応の文字列長 |
| コレクション | minitems / maxitems | slice / map 等の要素数 |
| 列挙値 | enum | 許可される定数値の列挙 |
| フォーマット | email / url / uuid 等 | 形式検証 |
| CEL | cel | CEL 式による柔軟な検証 |
go tool govalid ./... を実行すると、以下のようなファイルが生成されます。
// Code generated by govalid; DO NOT EDIT.
// エラー変数(errors.Is で個別判定可能)
var ErrUserNameRequiredValidation = govaliderrors.ValidationError{
Reason: "field Name is required",
Path: "User.Name",
Type: "required",
}
// バリデーション関数
func ValidateUser(t *User) error {
if t == nil {
return ErrNilUser
}
var errs govaliderrors.ValidationErrors
if len([]rune(t.Name)) > 100 {
err := ErrUserNameMaxLengthValidation
err.Value = t.Name
errs = append(errs, err)
}
// ...
if len(errs) > 0 {
return errs
}
return nil
}
// Validate() メソッド(govalid.Validator インターフェース実装)
func (t *User) Validate() error {
return ValidateUser(t)
}生成されるものをまとめると:
ValidateUser(t *User) error — バリデーション関数(t *User) Validate() error — メソッドErrUser<Field><Marker>Validation — エラー変数エラーは govaliderrors.ValidationErrors([]ValidationError)として集約されるので、errors.As で取得して個別のバリデーションエラーを確認できます。
cel マーカーを使うと、CEL(Common Expression Language)式で柔軟なバリデーションが書けます。
//govalid:cel=this.Type != 2 || value.Valid
OptionalField null.Intこれは「Type が 2 のとき、OptionalField が有効な値であること」を表現しています。
value: 現在のフィールドの値this: 構造体全体(フィールド間バリデーションに使う)govalid の生成コマンドは Makefile にまとめています。
govalid: ## govalid のバリデーター生成
go tool govalid ./...govalid は既存の生成ファイルを上書きしてくれるので、事前に削除する必要はありません。マーカーコメントを変更したら make govalid を実行するだけです。
導入時にいくつかハマったのでまとめておきます。
| 問題 | 原因 | 対処 |
|---|---|---|
required で 0 や false が弾かれる | ゼロ値チェックのため | 有効な値に 0 / false を含むフィールドには付けない |
同一パッケージで pkg.Const を指定してコンパイルエラー | 自己 import になる | プレフィックスを外す |
CEL の has(value) で生成コードがフォーマットエラー | 生成される式が長すぎて goimports が壊れる | value.Valid を使う |
go-playground/validator はタグベースで実行時にリフレクションで検証するので、実際にどんなバリデーションが走るのかはコードを動かすまでわかりにくいところがあります。govalid は実際にバリデーションのコードが生成されるので、何がどうチェックされるのかがそのまま読めるのが良いです。生成コードに対して errors.Is で個別判定できるのも、テストが書きやすくて助かっています。
あと、CEL 式でフィールド間のバリデーションをマーカーコメントに書けるのも便利です。go-playground/validator だとクロスフィールドのバリデーションは RegisterStructValidation でカスタム関数を登録する必要がありますが、govalid なら //govalid:cel=... の 1 行で済みます。