の続き的なところ。
結論としては、メッセージ出力順についての検討を次にやろうかな、に至るまでの経緯というか そのあたりの考察を まとめたものです。
メッセージ表示順序は検証優先度とは別物である
まず、検証順序ではなく、あえて優先度としています。
理由は、BeanValidationによる検証要件の順序が、スクリプトなどで実現するような順序というよりも、Groupによるグルーピングであるので、優先度 という表現の方が 個人的に しっくりくるからです。
同一優先度内における順序は保証されません。検証順序性を厳密に行いたいケースの扱いについては後述しますが、従来の設計思想のままでは ちょっと馴染みの悪い感覚になるのは間違いないと思います。
次に 表示順序についてですが、以前 検証結果のメッセージ変換を扱ったのと同様の理由で表示順序についても 検証する順序とは別の論理で実現するものであると考えます。
そもそもですが、例えば、画面の表示項目順に
名前は必須入力です。 生年月日は必須入力です。
のように出力したくても、BeanValidationの結果をそのまま使用すると 結果の出力順は検証都度変わります。
そもそも検証結果の戻り値の型が Set なので 順序を保証していません。
ところで、表示順と検証優先度は一致させるべきものでしょうか?
BeanValidationの設計思想が良くないということでしょうか?
おそらく NOです。
メッセージの遅延変換のところでも考察したところですが、主たる関心事が違うので 検証結果を表示要件と一致させる前提が 正しくないと考えた方が良いと思います。
なお、表示順序を考慮するために行った 私の経験としては、基本的に類似しているというところもあって スクリプト的な検証ロジックを用いて対応しました。
今 改めて考えてみると、本来は別物であるので 表示順の要件を検証優先度とするのは避けるべきであったかもしれませんが、現場の泥臭い世界は、大体こんなものなのかもしれません。
メッセージ表示順については、本質的には検証優先度とは別物であるということを念頭に 改めて別の機会で整理および実現方法の検討をしてみたいなぁと思っています。 (少なくとも、単純ソートで 表示メッセージが常に同じ順番になっています、というだけでは無いので)
検証要件と検証実施は別物である
例えば、Presentation層の 特に Controllerで 長々と検証要件を列挙せず、あくまでサービスの実行前に、#validate
メソッドにより、検証実施をするだけにして、検証要件自体は DomainObjectで表現しましょう、ということです。
加えて、検証要件は 宣言的もしくは 振る舞いの事前処理として強制しないことで、ロジックの主たる関心事に着目しやすい実装になると思います。
検証要件を振る舞いの事前処理として強制する、というのは、防御的プログラミングを指しています。つまり、防御的プログラミングをしない方が良い、ということです。防御的プログラミングの発想自体は NullPointer対策など 大変重要な実装方法だと思いますが、Nullチェック済みのValueObjectに対して、毎回毎回 防御的にNullPointerのチェックをするのは 冗長です。
Presentation層で受付した際に、FormからDomainObject(ValueObject)に変換したところで保証する という設計指針を持っておけば十分であると思います。
以前、私自身の書評*1にて 契約による設計 について 色々と批判的なことを述べていたわけですが、書籍が言おうとしていたことが、もしここで言っている検証要件と検証実施は別物という趣旨であったとするならば、おそらく私自身が記した批判は 的外れです。防御的プログラミングの前提の多くは ユーザー型(ValueObject)を用いることで ほとんど解消されます。
ということを丁度書いていたところで、@masuda220 さんが…
ロジックを実行する場所と、ロジックを記述する場所は、別のクラスであることが基本。
— 増田 亨. (@masuda220) August 25, 2018
別の言い方をすると、ロジックを実行する理由と、ロジックそのものは別の関心事。
ドメインロジック層のクラスにはロジックそのものを、アプリケーション層のクラスにはロジックを実行する理由を記述する。
そうです。同じアプローチですね。
— 増田 亨. (@masuda220) August 25, 2018
思わず、即質問してしまいました。
スクリプト的に実装したいが…
ちょうど(?)@making さんがライブラリを公開されました。
Released YAVI (Yet Another Validation for Java) 0.0.1 ヤバイhttps://t.co/E6nVPMMyS5
— Toshiaki Maki (@making) 2018年8月22日
Feedback is welcome
細かいハンドリングを考えた場合、このライブラリのようにスクリプト的な実装をした方が Annotationによる宣言的な実装よりも 分かりやすいというのは同感です。
加えて、依存ライブラリが全くないというもの 利用側からすると非常にありがたいところです。
使ってみようかな?という思いもありつつ、いったん 保守的に BeanValidationを軸に考えていきたいと思います。
今のところのアイディアとしては
SpringのBean Validationで複数の入力項目にまたがってチェックする。
のように @AssertTrueによる実装をして、検証優先度はGroupで、メッセージはmessageで 実装する、しかないかな?というところです。
求めている スクリプト的な検証 は
「こうじゃなかったら、エラーとして、こうだったら継続検証。メッセージとしては途中で発生したものを全て出力する」
というような、検証実施とメッセージ出力の粒度を調整することを求めているわけですが、@AssertTrue 方式だと、少なくとも メッセージ出力粒度にあわせて検証要件を分割しなければならないと思われます*2。
つまり、従来の手続き的な発想から、検証要件を宣言的に設計する、というように発想の転換を強いられることは間違いないと思います。 また、宣言的であるがゆえに検証優先度の高い検証要件のパーツ(メソッド)を何度も呼び出すような冗長的なロジックが発生するような気もしています。
いずれにしても、結構、この発想の転換は難しいと思っていて、やっぱり従来のスクリプト的なものを求めたい、となった場合は、BeanValidationに縛られない仕組みを準備する と割り切った方が良いかもしれません。下手に混ぜるとプロダクトの設計思想として とっちらかるので、何かしら整理は必要になると思います。
なお、私の過去の経験としては、必須/型桁チェック くらいのシンプルなものはAnnotationで表現しておいて、スクリプトな検証は 検証例外を発行するメソッドを設けて実施するようにしていました。検証も 必須/型桁チェック/スクリプト的なメソッド の優先度で行い、上位優先で検証不正があれば、以降の検証は行わないという感じです。
結果としてですが、なんだかんだと 順序性や出力メッセージに対して要望を求められていくことになり、Annotationで行っている検証ロジックと ほぼ類似のUtilを作ることになった上で、Annotationによる検証が、全てとは言いませんが それなりに駆逐されることになりました。
この経緯については、私自身の知見の少なさとプロジェクトの開発期間の短さなどもあり、ライブラリに精通するよりも さっさとUtilを作って 実装した方が早いという判断をしたわけですが*3、DDD的な設計思想に触れる中で、ちょっと考え方を変えていった方が良いかもしれないと 思うようになったというのが現在であり 、本考察をしようかな と思うに至った経緯という感じです。
ということで、私自身としては、一度 発想の転換をするべく努力をしてみて、後のことは それを踏まえて考えることにしました。
検証要件は事前/不変 条件でありドメイン知識
事前条件 または 不変条件 は ドメインにおける重要な関心事であると考えます。したがって、機能要件として DomainObject(ValueObjectやEntity)に対して、直接 または 直感的に分かりやすい形で 関連付けておくべきものであると思います。
例えば プロパティに対して Annotationにより宣言的に実装するという やり方もありますし、条件ロジックを呼び出すメソッドを共通的に扱うようなInterfaceを設けるという やり方が考えられます。
緩い方式だと、同一パッケージ内でパッケージプライベートクラスで作成して 名称で直感的に分かるようにしておいて DomainObjectから参照する というやり方でも良いかもしれません。
また、そのままでは実現できませんが、個人的には 事後条件についても 宣言的な検証をする仕組みを設けたい気がしています。
ざっくりなイメージだと、@ValidPostCondition
みたいなアノテーションがついていたら、配下の要素は #validatePostCondition
の時だけ採用するみたいな感じです。
イメージは @kawasima さんの
クラシックサーバサイドWebアプリを例に、バリデーションを各レイヤの境界での事前条件・事後条件と捉え、ユーザ向けのメッセージをどこで扱えばよいかを図にした。フロントエンドを切り離したときも各レイヤの責務は変わらず、インタフェースがメソッドコールからHTTPに変わっただけとしていける。 pic.twitter.com/0GgihWpaV3
— :SIer/kawasima (@kawasima) 2018年8月15日
の事前条件、事後条件のロジックの所在は DomainObjectまたはServiceに属する形で実装をして ドメイン知識として集めておきたい という感じです。
メッセージ表示順序はドメイン知識?
表示順序は 機能要件という意味ではドメイン知識ではあると思いますが、DomainObjectで扱うものかというと ちょっと違うかなというように思っています。
画面の表示項目順が変わる都度 DomainObjectを変更するか?というと 多分 それは筋が悪いと思います。
変更すべきは表示用のクラス(Form)や Controllerなどの Presentation層に依存するものに集めるべきだと思います。
さいごに
偶然にも、私が本記事に関することを考えているタイミングで 有効な情報が舞い降りてきて 有難い限りでした。 そのおかげで なんとなくイメージしていたものが 自分の中で まとまっていった気がします。
次は メッセージ表示順について 色々と考えていこうなぁと思っています。
上述でも別途 少し述べたように、簡単そうで 結構面倒くさいところが多いです。
単純ソートはダメというだけではなく、たとえば ControllerでFormに対して行った検証結果だけであれば良いのですが、レイヤーを跨った Application層(Service)で発生した例外かつ その内容が特定の表示項目に関連する情報だったら どうでしょう?
表示順だけではありませんが、表現力を上げようとすると Contoller(Presentation層)への負担は大きくなるのは仕方ないことかもしれません。そういった要件をできるだけ フレームワーク(ライブラリ)や設計思想をもって スッキリさせたいという 個人的な思いがあります。
どこまでのことが出来るか全くわかりませんし、その中で「こういう割り切りをしよう」となるとも思いますが、考えた上での割り切りであれば それを元にした設計方針となるので いずれにしても無駄にはならないはずです。
*1:http://vermeer.hatenablog.jp/entry/2017/08/16/124109
*2:途中で実行時例外を発行すると、おそらく他のvalidateが行われない
*3:そして、それは多分 正しい判断であったと今でも思います。実際 そうなるであろうことは 過去の宣言的実装の経験から ある程度 想定をしていました。