こんにちは、スープです。
フロントエンドでクリーンアーキテクチャをやっています。
近頃、フロントエンドにはクリーンアーキテクチャ(以下CA)は不要なのでは、という説が自分の中で持ち上がっているので、その話をします。
前提
複雑度がそれほど高くないSPAなフロントエンドアプリケーションの話です。
なぜ不要かもしれないか
- フロントエンドで表現するべき「ユースケース」が不在だから
- コントローラーは冗長なMiddle Manだから
- プレゼンテーション層と相性が悪いから
1. フロントエンドで表現するべき「ユースケース」が不在だから
ユースケースの役割
ユースケース層は、ドメインロジックのオーケストレーションを担い、ソフトウェアのユースケースを表現します。
例えば、「ユーザーを作成する」や「商品を購入する」のようなものです。
ソースコード全体を見なくても、ユースケースを見るだけで、何を解決するソフトウェアか理解できるべきでしょう。
- Entityのドメインオブジェクトを組み合わせて処理の流れを作る
- レポジトリの操作
- 読み書き処理が何発も走ることがある(Emailの重複を確認して、ユニークならユーザー登録させるとか)
- CQRSならレポジトリの操作がさらに増える
フロントエンドでのユースケース
バックエンドとフロントエンドの責務の切り方は様々にあり、それ次第でフロントエンドのユースケースに対する考え方は変わってきそうです。
そのため、プロダクトごとに設計を検討する必要があります。
API側にビジネスロジックが寄っている場合は、フロントエンドで表現するべきユースケースが不在なことがあります。
バリデーションすべき値がほとんどなかったり、単純なレポジトリの呼び出しのみしかやることがなく、その場合はユースケース層の存在意義が影を潜めます。
「記事をお気に入り登録する」を例に取ります。
- お気に入り登録APIを呼ぶ(レポジトリ操作)
- 成功したら
- 成功した旨を返却する
- 成功したら
- 不成功理由を返却する
- 成功したら
少し極端な例かもしれませんが、ただ単純にAPIを呼ぶだけですね。フロントエンドでやれることは他に特にありませんね。
これくらい薄いユースケースなら、直接コンポーネントからレポジトリを呼べたほうが良さそうに見えます。
ユースケースを準備する冗長さを飲む気になれません。
ユースケースが使える場合もある
「ユーザーを作成する」だと…
- バリデーション
- Email のフォーマットが適切か
- パスワードの長さが適切か
- ユーザー作成APIを呼ぶ(レポジトリ操作)
- 成功したら
- ストアにユーザー情報を詰める(レポジトリ操作)
- ユーザー情報を返却する
- 成功したら
- 不成功理由を返却する
- 成功したら
「商品を購入する」だと…
- バリデーション
- クレカの期限切れチェック
- 商品購入APIを呼ぶ(レポジトリ操作)
- 購入が成功したら
- カートを空にする
- 購入情報を返却する
- 不成功だったら
- 不成功理由を返却する
- 購入が成功したら
これらの例のように、複雑性が増してくると、ユースケース層の導入意義が増してきそうですね。
処理の流れを抽象的に管理したくなるからです。
ただし、コンポーネント側でバリデーションした上で呼び出すという契約にする場合は、ユースケースでのバリデーションは不要になりますね。
ドメインオブジェクトの呼び出しが減るため、ユースケース層の存在意義が減りそうです。
2. コントローラーは冗長なMiddle Manだから
入出力の型変換を行うくらいで、あとはユースケースを呼び出すのみで、非常に薄い Middle Manです。
Martin Fowler 氏の Refactoring に出てくる Remove Middle Man ができそうです。
https://refactoring.com/catalog/removeMiddleMan.html
ドメインロジックをビューに漏れ出させないために、ここでDTOに詰め替えて返却する必要があると思っていました。
でもそんなことはフロントエンドでは基本的になく、ビューではDTOではなく、ドメインオブジェクトを扱えるほうが都合が良いことの方が多いです。
むしろ、表示をメインの責務としているフロントエンドにおいては、表示で使うロジックをドメインオブジェクトに抽出するのだと今は考えています。
3. プレゼンテーション層と相性が悪いから
Uncle BobのFlow Of Controlによると、コントローラーから呼び出されたInteractorが内部でプレゼンテーションを呼び出し、プレゼンテーション経由で元の呼び出し元に直接応答するようになっているみたいです。
コントローラーが呼び出されているのに、代わりにプレゼンテーションが直接呼び出しに応答するというのが、web通信を挟まない環境では実際的ではないかと思われます。
じゃあどうしよう
我々が欲しかったのは、CAというよりかは、変更の影響の見通しの良さではないでしょうか。
以下は案の一つです。
当然ですが、プロダクトの性質によって適切な設計は変わってきます。
- コントローラー、プレゼンター、ユースケースは要らない。
- ドメインロジックのオーケストレーションが必要になることがあれば、そのときに初めてユースケース層を導入する(YAGNI)
- コンポーネントからレポジトリを呼ぶ
- Context 等でレポジトリのインスタンスを渡せば良い
- レポジトリはドメインオブジェクトを返却する
レイヤー数は2か3で事足りそうです。
- Entity
- Value Objectやドメインサービス等のドメインロジック
- Repository
- 永続化
- ドメインオブジェクトを返却する
- Infrastracture
- ビュー
- ロギング
...
ただし、ViewModelなどで行っていた、日付等のフォーマット操作をどうするかはまだ検討する必要があります。
Entityに生やすか、Repositoryの戻り値をViewModelとするかになるでしょうか。いい案があれが教えてください。
お気持ち
CAに限った話ではないですが、どんなプリンシプルもそのエッセンスを抽出し、プロダクトの性質を踏まえた上で設計をしていく必要があります。銀の弾丸は存在せず、解決しようとする事柄に応じて最適な設計を探っていく必要がありそうです。
また、抽象化等のソフトウェアの複雑性を高める決定は、その新たに生む複雑性を上回るメリットがあるなら受け入れられます。
そのトレードオフを飲めない場合は、そのコードベースの複雑性を押し上げる決定は避けるのが賢明だと思われます。