バリデーションはどこで行うべきか?
バリデーションの実装箇所について一度まとめてみる
SPA+RestAPIのWEBアプリを想定
調査前予想
- フロント
- UXを考えるとフロント側でのバリデーションは必要(DBアクセスが必要ないものに限る)
- API
- DBアクセスが必要ないもの
- controllerのFormバリデーション
- DBアクセスが必要なもの
- Service
- DBアクセスが必要ないもの
調査後結論
- フロント
- フロント側でのバリデーションは必要
- 基本的にDBアクセスが必要ないものに限る
- API
- DBアクセスが必要ないもの
- ControllerのFormバリデーション
- DBアクセスが必要なもの
- Service
- フィールドごとにメッセージを出力したい場合は、Serviceに実装し、Formのカスタムバリデーションで呼び出す
- DBアクセスが必要ないもの
早速まんまな記事発見
ドメイン駆動開発だと値オブジェクトでバリデーションを実装するのか
値オブジェクトで実装する方が、入力(controller)で実装するよりも安全性が高いっていうのは確かにそうだわ
現在、この世で動いているシステムのほとんどは、一つの入力フォームに対して、フロントとサーバ両方でバリデーションをかけていて、その用法が主流のようです。
どういうことかというと、一つの入力データに対して、フロント、そしてサーバと2つの側面からチェックを働かせているのです。
そうなのね
まあSPA+APIならどちらもやるしかあるまいて
みんなの回答まとめ
- サーババリデーションは必須
- これがないとシステムがバグる
- クライアントバリデーションは必須ではないがあったほうがよい
- クライアントバリデーションも入れることでUXを最大化する
- サーバサイドに合わせる
- 理想はサーバサイドと同等、そうでない場合はサーバサイドより常に軽めに
次はフロントではDBアクセスが必要なバリデーションは行わない(仮説)について調べるか
経験則に過ぎないが、
- 実装が面倒(バリデーションごとにAPI作成?)
- 例えばユーザーメールアドレスは一意であるべきというのをフロントが管理しているのは少々ファットに思える
- バリデーション実行時とDBへの登録時のタイムラグを考えると、フロントで通ってもサーバ側で通るとは限らない
2つ上の内容とほぼ同じ
ただフロントからはDBアクセスが必要なバリデーションは行えないと言っているが、それは設計による
データベースの状態を用いた入力値のチェックをしたい場合があります。 データベースの状態チェックはトランザクション配下で行ったほうが良いケースがあるため、Bean Validationを使用するのではなくServiceなどでバリデーションを行うことをおすすめします。
まあそうだよね
ちょっと調べた感じ最初の認識で問題なさそう
ちらほらFormでDBアクセスしているような記事もあったけど…思想の問題か?
それにしても今回もTransactionか
そろそろ調べんとな
問題はserviceでDBアクセスが必要なバリデーション→エラーがあった場合にどうフロントで表示するかなんだよなあ
直近ではserviceで例外発生→controllerでキャッチ&エラーメッセージで判別→フロントに文字列として返すってやったけど流石にもっといい方法あるだろ
ここだとcontrollerでキャッチしてモデルに詰めて返してる
今回は使えないな
お!DBアクセスが必要ならドメイン層でやるって書いてある!
以下引用
ユーザーが入力した値が不正かどうかを検証することは必須である。 入力値の検証は大きく分けて、
1.長さや形式など、文脈によらず入力値だけを見て、それが妥当かどうかを判定できる検証
2.システムの状態によって入力値が妥当かどうかが変わる検証
がある。
1.の例としては必須チェックや、桁数チェックがあり、2.の例としては 登録済みのE-mailかどうかのチェックや、注文数が在庫数以内であるかどうかのチェックが挙げられる。
本節では、基本的には前者のことを説明し、このチェックのことを「入力チェック」を呼ぶ。 後者のチェックは「業務ロジックチェック」と呼ぶ。業務ロジックチェックについては ドメイン層の実装を参照されたい。
本ガイドラインでは、基本的に入力チェックをアプリケーション層で行い、 業務ロジックチェックは、ドメイン層で行うことをポリシーとする。
Webアプリケーションの入力チェックには、サーバサイドで行うチェックと、クライアントサイド(JavaScript)で行うチェックがある。 サーバーサイドのチェックは必須であるが、クライアントサイドでも同じチェックを実施すると、 サーバー通信なしでチェック結果が分かるため、ユーザビリティが向上する。
後日追記
- ビジネスルールのエラーをフィールド毎に出力する必要がある場合、Controller側の仕組みを利用する
- チェックロジック自体はServiceとして実装し、Bean ValidationからServiceのメソッドを呼び出す方式で実現することを推奨する
とのこと。つまり、フィールドごとにメッセージを表示したい場合は、FormでDBアクセスも許容するということ。
DBアクセスが必要な場合にはserviceでチェックして、例外を投げるってのはあってそう
このサイトも参考になりそう
今まではSPAを前提としたサイトじゃなかったし
↓これ見る感じ、エラーメッセージをレスポンスに詰めて返せばよさそう
ただ例外を一回controllerで受け取るのか、serviceで出しっぱなしにするのかはわからん
最終的なコード
@Slf4j
@RestControllerAdvice
public class ApiControllerAdvice {
@ExceptionHandler(InvalidArgumentException.class)
public ResponseEntity<ValidationErrorResponse> handleInvalidArgument(InvalidArgumentException ex) {
log.warn(ex.getMessage(), ex);
ValidationErrorResponse re = new ValidationErrorResponse(
BAD_REQUEST.value(),
BAD_REQUEST.getReasonPhrase(),
ex.getErrors().stream().map(error ->
new ValidationErrorResponse.Error(error.getMessage(), error.getField())).toList());
return new ResponseEntity<>(re, BAD_REQUEST);
}
}