Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rejected Valueの表示メカニズムの提供 #47

Closed
masuda220 opened this issue Feb 1, 2017 · 7 comments
Closed

Rejected Valueの表示メカニズムの提供 #47

masuda220 opened this issue Feb 1, 2017 · 7 comments

Comments

@masuda220
Copy link
Contributor

masuda220 commented Feb 1, 2017

バリデーションの失敗時に、拒否された入力値をフォームに表示する仕組みを、ドメイン層のクラスから取り除きたい

概要:

ドメインオブジェクトに、不正な入力値を保持したくない
(ドメインオブジェクトにバリデーション時の不正値を扱うコードを書きたくない)

背景:

th:field でバインディングした場合、不正値を再表示するためには、ドメインオブジェクトが入力された文字列を持ち、バリデーション結果を持ち、toString()で、表示内容を変える仕組みを記述している。

このためのコードは、本来、ビューで解決すべき内容であり、ドメインオブジェクトを不適切に汚染している。

解決案:

th:field ではなく、

th:id, th:name, th:value を明示的に使う
th:value で FieldError#getRejectedValue()を表示する

効果/目標:

ドメインオブジェクトからバリデーションの不正入力値を扱うコードを取り除く
Thymeleafのテンプレートの記述を「できるだけ」簡潔にする

  • th:field方式に比べ、id,name,value の記述の煩雑さを緩和する工夫
  • FieldError#getRejectedValue() を参照する記述の簡略化の工夫

代替案1:

Thymeleafではなく、JavaScriptで実現する

仕組みが複雑になり、可読性、保守性に問題が多い

留意事項

・Thymeleaf の用意している拡張の仕組みの利用を検討
http://www.thymeleaf.org/doc/tutorials/2.1/extendingthymeleaf.html

・BindingResultから、FieldErrorsあるいは、RejectedValuesを、テンプレートに渡す方法

・公開されたレポジトリでの提供(依存性解決)

@masuda220
Copy link
Contributor Author

masuda220 commented Feb 4, 2017

代替案2:

(従来通り)ドメインオブジェクトにソーステキストを持たせる (boolean validによって出しわけはなくす?)
これが、やはり現実的か?

代替案3:

ドメインオブジェクトではなく、DTO(フォームオブジェクト)にする
これはやりたくないなあ。

@irof
Copy link
Contributor

irof commented Feb 10, 2017

バリデーションの失敗時に、拒否された入力値をフォームに表示する、などを行ってみたコードです。
(FormController.javaなど。)

https://github.com/irof/pracswing-spring-boot/tree/4bf1b75acff8e288e9e8642e264ef620e173a81a/alpha-web/src/main/java/alpha/spring/mvc

@masuda220
Copy link
Contributor Author

rejectedValueの扱いの結論を要約すると、

(1)rejectedValue をモデルが持つ必要はない

Spring MVC の model オブジェクトが、入力内容を保持している。hasErrors のときのビューを表示するために、ドメインオブジェクトを渡す必要はない。
hasErrors の場合、model に何も add する必要がないということ。 BindingResultを使って、Spring MVC がよしなにやってくる

(2) 数値や日付へのバインディング

文字列を一つ持つコンストラクタを宣言した値オブジェクトを使う。
コンストラクタのなかで、int ,BigDecimal, LocalDate などへの型変換を失敗したら、
Spring の TypemismatchException を返す

エラーメッセージは、メッセージ定義ファイルに、 typemismatch.型名のフルパスを記述するばOK。
それぞれの独自型では、 java.time.LocalDate 型のメッセージを定義して、日付系の型変換は、それを共通で使うのもありかも。 (日付は、yyyy-mm-dd で指定してください、とか)

(3)結論

ドメインオブジェクトは、rejectedValue や、invalid なスタータスについて、何もコードを書く必要はない。
ドメインオブジェクトは、適切な状態でしか存在しない、という約束事でよい。

hasErrors のときに、model に何か addAttributeする必要はない。

ようするに、rejectedValue は、コンストラクタで型変換を試みて、失敗したときにTypemismatchException を返すことだけを記述すれば、その他の記述は不要。

@masuda220
Copy link
Contributor Author

@irof さんのサンプルは、Spring MVC デフォルトの getter/setter でした。
InitBinder で、Direct Field Access に切り替えると、Spring MVC のエラー時の挙動が、getter/setterとは変わってしまい、うまくいきません。

getter/setter の場合:
文字列一つのコンストラクタでのバインディングも、正常時のみtoString()による出力もうまくいく。

direct field access の場合:
th:field を *{email} にすると、エラー値を入力した時に、Spring MVC はエラーを検知できない。( rejected field が email.value になってしまうため。
th:field を *{email.value} にすると、コンストラクタでのバインディングも、toString() 出力もできない。 

Direct Field Access の場合も、getter/setter と同じ挙動にする方法はないか?

@haljik
Copy link
Contributor

haljik commented Mar 29, 2017

${#fields.hasErrors(email*)}でエラー検知できないですかね?

@masuda220
Copy link
Contributor Author

エラーメッセージの表示はそれでいけます。
問題は、th:field のほう。single string constructor と toString()のパターンが使えなくなる。

@masuda220
Copy link
Contributor Author

masuda220 commented May 5, 2017

JSUGの大野さんに調べてもらい、意見交換した結果。

状況:

th:field で指定したバインドパスが、バリデーションエラーを起こしているかの判定ロジックは以下のメソッド

AbstractErrors#isMatchingFieldError(String field, FieldError fieldError)

ここで、th:field:age の指定だと、age.age のエラーを検知できない。
ここは、Spring MVC の中核なので、簡単に変更とかは、対応してもらえないだろう。

結論:

th:field も age.age を指定するようにする
(Single String Constructorを使わない)

バインディングの型変換の問題は、以下で対応

int ではなく Integer を使う ( デフォルトの表示を0ではなく、空白にするため)
LocalDate へのバインディングは、Spring の @DateTimeFormat アノテーションを使う

バインディング時に特殊な加工(どんな?)が必要な場合は、property editor の追加で対応することを検討する。

という方向で、isolating-the-domain のサンプルを書き直す

当初の目的である、reject value の心配ごとは、MVC側に任せることは、この方法で実現できる。 view の記述が若干冗長になるが、 th:field と th:errors のパス記述が一致するメリットはある。

いったんクローズ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants