- なぜ UseCase が肥大化するのか
- UseCase は「必須レイヤー」ではない
- Flow を返す UseCase と suspend UseCase の境界
- Command と Query を分けるという現実解
- UseCase を作らないという選択肢
- 非同期を UseCase に閉じ込めすぎない
- この章のまとめ
なぜ UseCase が肥大化するのか
UseCase は、本来「アプリケーションとしての振る舞い」を表現するための概念です。しかし Android 開発の現場では、次のような理由で簡単に肥大化します。
- 画面単位で UseCase を作り始める
- Repository を直接触らせないための中継層になる
- 非同期処理(Flow / suspend)の置き場として使われる
その結果、UseCase は「何をしているのか分からないが、とりあえずここを通す場所」になりがちです。これは設計ミスというより、役割が曖昧なまま使われ続けた結果 です。
UseCase は「必須レイヤー」ではない
多くのアーキテクチャ解説では、UseCase(あるいは Interactor)は必須レイヤーとして描かれます。しかし、本書の立場は明確です。
UseCase は、必要になったときにだけ導入する道具である。
UseCase が価値を持つのは、次のような場合です。
- 複数の UI から同じ振る舞いを呼び出す
- 一連の操作を「1つの意味ある処理」としてまとめたい
- ドメインの判断を UI から切り離したい
逆に言えば、単に Repository を呼ぶだけの処理や、画面専用の薄い処理に UseCase を挟む必然性はありません。
Flow を返す UseCase と suspend UseCase の境界
Android では、UseCase が Flow を返すのか、suspend 関数なのかで悩む場面が頻発します。この問題の本質は、非同期 API の選択ではありません。
判断基準は、
- その処理は「状態を監視するもの」か
- それとも「1回の命令」か
という点にあります。
- 状態を継続的に監視する → Flow
- 1回きりの処理を命令する → suspend
これを曖昧にすると、UseCase の責務がぼやけ、ViewModel 側も扱いづらくなります。
Command と Query を分けるという現実解
UseCase 地獄を抜けるための、非常に実践的な解決策が Command / Query の分離 です。
- Query:状態やデータを取得する
- Command:状態を変化させる命令
これを分けるだけで、次のような効果があります。
- Flow を返すのは Query だけになる
- Command は suspend 関数として明確になる
- 名前と責務が一致しやすくなる
すべてを UseCase に押し込めるのではなく、「役割ごとに分ける」ことで、構造は驚くほどシンプルになります。
UseCase を作らないという選択肢
本書では、あえてこの選択肢も提示します。
UseCase を作らなくてもよいケースは、確実に存在する。
たとえば、
- 画面専用の処理である
- ViewModel から直接呼んでも責務が明確
- 将来的に再利用される見込みがない
このような場合、無理に UseCase を挟むよりも、ViewModel と Repository の間で完結させた方が、構造が分かりやすくなることもあります。
重要なのは、「UseCase があるかどうか」ではなく、判断がどこに集約されているか です。
非同期を UseCase に閉じ込めすぎない
Android 開発では、UseCase を導入するときに 「UseCase は suspend 関数にするもの」 「非同期処理はすべて UseCase に隠すもの」 という設計になりがちです。
しかし、非同期処理をすべて UseCase に押し込めると、設計は次第に歪んでいきます。
まず起きやすいのが、ViewModel が極端に薄くなる という問題です。
- UseCase を呼ぶ
- 結果をそのまま UI に流す
だけの存在になり、 「画面としてどういう状態を持つか」という判断が、どこにも存在しなくなります。
次に問題になるのが、エラーやローディングの責務の所在が曖昧になることです。
非同期をすべて UseCase に閉じ込めると、
- ローディング状態は UseCase が持つのか
- エラーを UI 向けの形に変換するのは誰なのか
- 再試行やキャンセルはどこで扱うのか
といった判断が、UseCase と ViewModel の間を漂い始めます。
結果として、
- Result や sealed class が UseCase から返ってくる
- UI の都合が UseCase の戻り値に混ざる
- 「これはドメインの話?UI の話?」という迷いが生まれる
という状態になります。
もう一つ、見えにくいですが深刻なのが、 UI 状態とドメイン判断の境界が曖昧になることです。
非同期というのは、
- いつ開始するか
- いつ終わるか
- 途中経過をどう扱うか
といった、アプリ全体の都合に強く結びついた関心事です。
一方で、ドメインが関心を持つのは、
- 何が有効か
- どう分類されるか
- どんなルールで扱われるか
といった 意味や判断 です。
この二つは、変更理由がまったく異なります。
非同期を無理に UseCase に押し込めると、 この異なる変更理由が 1 つの関数の中に混ざり込み、 「なぜこのロジックがここにあるのか」を説明できなくなります。
本書で言いたいのは、 非同期を UseCase から排除すべき、ということではありません。
重要なのは、
- これはドメインの判断か
- それとも UI 状態を組み立てるための非同期制御か
を、意識的に分けることです。
- ドメインの判断そのものは UseCase に置く
- ローディングやエラー、画面状態の組み立ては ViewModel が担う
この役割分担が見えていれば、
- UseCase が増えすぎない
- ViewModel が責務を持った存在になる
- 「UseCase 地獄」に陥りにくくなる
という状態に近づきます。
非同期は強力ですが、同時に設計を曖昧にしやすい要素でもあります。 だからこそ、「とりあえず UseCase に入れる」 という判断を避け、 その非同期が 誰の都合なのか を、一度立ち止まって考える必要があります。
この章のまとめ
UseCase は、
- 作ること自体が目的ではない
- 責務を明確にするための選択肢の1つ
に過ぎません。
Command / Query の分離や、UseCase を作らない判断も含めて、「なぜこの形を選んだのか」を説明できることが、設計としての完成度を高めます。
次章では、エンティティやモデルを Android 文脈でどう設計するかを掘り下げていきます。