この記事は うるる Advent Calendar 2019 2日目の記事です。
はじめに
この記事のターゲット
- オニオンアーキテクチャ/レイヤードアーキテクチャをやろうとしていてバリデーション実装で悩んでる
- Domain層でバリデーション実装したらどうなるのかイメージを持ちたい
この記事ではオニオンアーキテクチャを採用しているプログラムにて
Domain層 にバリデーションを実装してみて良かった点と課題に感じた点を書いたものになります。
(具体的なプログラムの全容などは本項では記述しておりません)
あくまでも個人が感じた感想なので参考程度にとどめて頂ければと思います。
バリデーションとは
API開発などクライアントから情報を受け取って処理をするようなプログラムでは
クライアント側からどのようなリクエストが飛んでくるか分かりません。
そのためサーバー側はクライアントから渡された値に対して精査を行い
基準にそぐわないリクエストの場合は処理を中断してクライアントにメッセージを返してあげる必要があります。
これがバリデーション実装です。
オニオンアーキテクチャとは
プログラムをUI層/Service層/Domain層/InfraStructure層の4層に分ける レイヤードアーキテクチャ
の考え方を元に、
Domain層とInfraStructure層の依存関係を逆転させたアーキテクチャです。
下記の記事が綺麗にまとまっているのでそちらを参考にしてください。
ここでは詳細を記述することは省きます。
https://qiita.com/little_hand_s/items/2040fba15d90b93fc124
バリデーション実装はどこでやるべきか
さて、ようやく本題です。
オニオンアーキテクチャを採用して開発している方々のバリデーション処理の実装箇所は人によって意見が異なっており
- 基本に忠実に! Domain層に書くべきだ!
- 全量やったらドメインモデル汚れるだろ! UI層に書くべきだ!
大体この2つで議論されている印象です。うん、どちらもわかる、わかるよ笑
ただこのままだと埒があかないので、それぞれのメリデメをざっくり書いてみましょう。
Domain 層で実装 | UI 層で実装 | |
---|---|---|
メリット | バリデーションがドメイン層にまとまるので プログラムを理解しやすい | Domain 層でのチェックは ビジネスロジックに集中できる |
デメリット | 低次元なチェックも含まれるため ドメイン層が肥大化しやすい | UI層とDomain層の役割の分割で 混乱しやすい |
Domain層に書くのは比較的簡単ですが、低次元なチェックも詰め込まれやすいので肥大化リスクを抱える。
UI層に書くのは上手く書ければDomain層が綺麗になるものの、判断に迷うポイントがあるので初期設計が重要そうです。
で、お前(たち)はどうしたんだと言うと…
上でも触れた通りうちのチームでは Domain層に書く ことで実装を進めました。
理由は下記の通りです。
- オニオンアーキテクチャでの実装が初の試みで、いきなり完璧なものを書くには敷居が高かった
- チームが立ち上げ期で経験も若いので、分かりやすさ重視で進めたかった
- 設計に迷う時間は短くしたかった
実装イメージを出してみましょう。
プラットフォームは PHP7, Laravel で 公開日は必須/日付のフォーマットである/未来日ではない
を表現したクラスの例です。
class AnnouncementDate
{
/**
* @var null
*/
protected $value = null;
/**
* AnnouncementDate constructor.
* @param string|null $value
*/
public function __construct(?string $value)
{
$this->check($value);
}
/**
* @param $value
*/
private function check($value)
{
if ($this->requiredSelect($value)) { // 必須チェック
if ($this->dateFormat($value)) { // 日付フォーマット
if ($this->futureThan($value)) { // 未来日
$this->value = $value;
};
}
}
}
/**
* @return null|string
*/
public function string(): ?string
{
return (empty($this->value)) ? null : $this->carbon()->toDateString();
}
}
※各バリデーションチェックの具体的な処理はTraitで外出ししてます。
※今回の例ではExceptionは投げていませんが強制的に止めるならExceptionを投げても良いと思います。
(Exceptionを投げずにエラーを積み上げる方法についてもいずれ書けたらいいなあ)
実装してみた感想
良かった点
Domain クラスを見るだけでどんなバリデーションチェックをしているのか一目瞭然
Domain層実装案のメリットにも記載した通り、
バリデーションが書かれているレイヤーが統一出来ているので直感的に分かりやすくなり、
実装もスピーディに進めることが出来ました!
バリデーションチェックを簡単に追加/削除出来る
11月にまだプログラミング経験の若いメンバーがチームに参画したのですが、
その時に説明がしやすかったです。
「このチェックが足りてないよ」という指摘も実装もやりやすく
Domain層実装パターンは効果があったのかなと感じております。
悪かった点・悩んだ点
低次元なチェックがDomain層に入り込む
日付フォーマットのチェックなども Domain 層に書いていったのですが、
ビジネスロジックというよりも入力規則な感じに近く違和感は正直ありました。
今回、我々が実装したものについては数はそこまで多くないので割り切っておりますが、
ビジネスロジックが非常に複雑で入力規則チェックを混入させたく無い場合は
入力規則だけはUI層で弾く、と言う書き方も有効かと思います。
( Laravel で言うところの Request クラス)
特定の入口からの特別な処理に対応しにくい
例えば 画面AからはXは必須項目だけど、B画面ではXは任意項目
みたいな事があると
Domain クラスに対して Bool を引数に取らないと実装が難しくなります。
今回、我々はチェックフラグのようなBoolを引数に追加することで解決(もとい逃げた)したのですが
UI層実装案を採用していたらUI層で必須か否かをハンドリング出来るので
もう少し綺麗に書けたんじゃないかなあ、と思った次第です。
まとめ
シンプルに書きたいのであれば Domain 層実装パターン 、
Domain層はビジネスロジックだけ集中して書きたい場合は UI 層実装パターン で書けると良いのかなと思った次第です。
しかしながらオニオンアーキテクチャって難しいですね。人によって持つ意見が違うので悩みの連続です。
チームで開発する場合は共通認識を持ちながら開発をする必要があるのでチームの理解度や力量も重要になります。
Domain層での実装・UI層での実装、
どちらを取っても何かしらの悩みポイントは出ると思うので実装しようとしているサービスの複雑度や
チームの意見・力量などと加味して上手く判断していく事が重要なのかなと思います!
あとがき
Advent Calendar 2日目でした。
明日3日目は Kazuna Doue さんによる記事を乞うご期待!
https://adventar.org/calendars/4548
参考