okmkm.log

govalid でドメインバリデーションのコード生成を導入した

2026年02月28日に公開


記事一覧に戻る
  • X
  • はてなブックマーク
okmkm.log
  • 記事一覧
  • Zennの一覧
  • Github

© 2025 okmkm. All rights reserved.

はじめに

個人開発の Go バックエンドで、ドメイン層のバリデーションに govalid を導入しました。

govalid は構造体フィールドにマーカーコメントを書くだけで、型安全なバリデーションコードを自動生成してくれるツールです。リフレクション不使用でゼロアロケーション、go-playground/validator と比べて 5〜44 倍高速とのこと。早すぎる。

勤務先では go-playground/validator を使っていたのですが、個人開発ではzennの記事をみて気になっていた&リフレクションなしで動くものを試してみたくて govalid を選びました。

この記事では、govalid の基本的な使い方と、導入時にハマったポイントをまとめていきます。

技術スタック

  • Go: 1.26
  • govalid: v1.8.0

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 / maxlengthUnicode 対応の文字列長
コレクションminitems / maxitemsslice / map 等の要素数
列挙値enum許可される定数値の列挙
フォーマットemail / url / uuid 等形式検証
CELcelCEL 式による柔軟な検証

生成されるコード

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 マーカーを使うと、CEL(Common Expression Language)式で柔軟なバリデーションが書けます。

//govalid:cel=this.Type != 2 || value.Valid
OptionalField null.Int

これは「Type が 2 のとき、OptionalField が有効な値であること」を表現しています。

  • value: 現在のフィールドの値
  • this: 構造体全体(フィールド間バリデーションに使う)

Makefile

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 行で済みます。

GitHub - sivchari/govalid: Up to 45x faster 🚀 Auto generate type-safe validation code for structs based on markers.

Up to 45x faster 🚀 Auto generate type-safe validation code for structs based on markers. - sivchari/govalid
github.com

Goの次世代バリデーションツールgovalid

zenn.dev