【ReportAsSingleViolation】そのカスタムバリデーションを車輪の再発明にならないために【既存アノテーションをまとめる】


なぜSpring Bootで独自のバリデーションを作りたいの?

API開発を複数人で進めていると、APIで同じ項目が出てきます。

  • 注文番号(orderNo)
  • 商品コード(itmCd)
  • 管理番号(mngNo)
  • ユーザーID(usrId)
  • 等々...

同じ項目であれば、同じアノテーションでバリデーションする事が当たり前。でも、ドキュメントやルールの周知を頑張っても意思疎通がとりきれずアノテーションがズレたものがポロポロと。。

そんなときの解決策の1つとして、カスタムバリデーションがあると思っています。

カスタムバリデーションのパターン

大きく分けて、2つあると思っています。

  • 完全オリジナルなロジックが必要なもの
  • 既存バリデーションの組み合わせで済むレベルのもの

後者を作成するとき、自前でロジックを組んでいませんか?

@ReportAsSingleViolationの利用検討をお勧めいたします。

ReportAsSingleViolation

一言でいうと「アノテーションをまとめるアノテーション」

どうゆうことか、以下ソースの後にポイントを記載します。

UsrId.java
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Size(min = 8, max = 10)
@Pattern(regexp = "^[a-zA-Z0-9]*$")
@ReportAsSingleViolation
public @interface UsrId {

    /** エラーが発生した場合のエラーメッセージ. */
    String message() default "ユーザーIDが正しく設定されていません";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Constraint(validatedBy = {})

{}なんです。
やりたいことが既存の組み合わせで完結する場合、ロジックを記述するバリデータークラスは必要ありませんよね。
実際、このカスタムアノテーションはアノテーションの組み合わせだけで完結しています。

message() default "xxx"

@ReportAsSingleViolationを利用するとデフォルトメッセージが機能します。これがすごい。
バリデーション動作時には@Size@Patternそれぞれのメッセージ2つは出力されず、自分が設定したメッセージだけが出力されます。
つまりアノテーションがまとまっているんです。

さいごに

@ReportAsSingleViolationを作ればファイル1つでカスタムバリデーション完成です。

カスタムバリデーションって難しそうだから忙しいし、今のプロジェクトへの適用は後回し。。という固定概念を破壊するくらいの手軽さ。

しかし効果はしっかり発揮します。
サンプルではユーザーID 項目を使う複数のDTOクラスやGETパラメータに以下がバラまかれるのを防ぎました。
- @Size(min = 8, max = 10)
- @Pattern(regexp = "^[a-zA-Z0-9]*$")

ルールとして@UsrIdを使おう!というのは必要になります。ですが、特定の引数を付けたバリデーション利用を徹底してください。というルールよりはマシではないか思っています。

是非お試しくださいませ。