なぜ 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 文脈でどう設計するかを掘り下げていきます。