この情報は古いです。リライトしました。
http://hikouki.hateblo.jp/entry/2019/05/31/164944
この記事は CrowdWorks Advent Calendar 2017 の14日目です。
エンジニアリーダーの @hikouki です。
0からDDDでアプリケーションを立ち上げ中ですが、バリデーションの壁にぶち当たったので、
「バリデーション」はどう扱えばいいのか考えてみました。
バリデーション
ここでは、ユーザー(外部システム)入力値に対する検証に限定します。
どこでユーザー入力値を検証するべきか
ユーザー入力値のバリデーションは全て、Presentersion層で行うべきだと考えました。
理由は、「Application層に来た時点でユーザー入力値ではない」からです。
なので、Application層の引数が不正な場合は、例外(内部エラー)として扱うようにします。
どこにバリデーションルールを定義するべきか
バリデーションルールには、業務知識を含むものと含まないものがあります。
例えば、以下のような感じです。
業務知識
- 注文番号は15桁
- emailは一意
- 名前は必須
業務知識でないもの
- 入力値Aの状態によって、入力値Bのルールが変わる(Presentersion層の都合)
まず、業務知識は ValueObject に定義して、
業務知識でないものはPresentation層に定義することを考えました。
業務知識をDomain層に書くことは正しいし、そうすべきです。
ですが、チームで話した結果、Domain層にバリデーションルールを定義しないことにしました。
理由は以下のような感じです。
- Presentation層と、Domain層でバリデーション定義が散らばる
- 場合によって、バリデーションルールが変わる(記事が下書きの時は、内容は空でも良いなど)
業務知識がドメイン層から染み出してしまうので、かなり悩みました。
ただ、コードの見通しや変更しやすさを優先しています。
DBにアクセスする必要があるものはどう検証するか
これもどの層で検証するか議論が分かれます。
なるべく各層の依存関係をシンプルにしたいので、Presentation層からInfrastructure層を呼び出すことは避けたいです。
そこで、Application層で検証することにしてみます。
Application層で検証する場合、それはユーザー入力値ではないので、検証に失敗した場合、例外(内部エラー)を返します。
なので、APIの呼び出し元で事前に値を検証する必要がありそうです。
値を検証するためだけのAPIを用意して、検証した後で、呼び出してもらうような設計にしました。
重複しやすい値が高頻度で入力されるシステムの場合は、耐えられない気もしますが、
今のところ、そのようなデータを扱っていないので、この設計に落ち着いています。
バリデーションエラーをどう表現するか
不正な入力値の場合、バリデーションエラーである旨、APIの呼び出し元に返す必要があります。
この記事では、「ある特定の社内サービスからgRPCで通信するマイクロサービス」を前提にします。
Viewを持たないので、リッチなメッセージを返す必要はなさそうです。
また、UXを良くする為に、エンドユーザーには早い段階で入力値の検証を行いたいことがあります。
その為、エンドユーザーへの入力値バリデーションは、マイクロサービスを使う側の責務としました。
マイクロサービス側は、期待していない値が入力された場合、例外(INVALID_ARGUMENT)として扱い、適切に障害通知して必要な情報をログに保存します。
APIの呼び出し元は、例外を受け取った場合、処理が継続不可能な状態として 「500エラー」の画面を表示させます。
まとめ
チーム内でも意見が分かれる部分があり、かなり熱く議論しました。
DDD的な考え方から行くと、やっぱりDomain層にバリデーションルールを定義した方がいい気もしています。
ただ、メンバーの経験や、すでにある事例を参考にして意思決定を行いました。
バリデーションや例外の扱い方はアプリケーションの要件によって大きく変わる部分なので、一つの参考資料として見て頂けると幸いです。