SQL の条件を固定しない
DAO の責務を整理して、ドメインに意味を寄せる設計改善
はじめに
Android アプリ開発において、 「とりあえず動く」状態から一段上の設計に進もうとすると、 ViewModel・UseCase・Repository・DAO の責務の境界で悩むことが多い。
今回は、
- SQL で条件を固定していた実装を見直し
- 条件をパラメータ化して SQL の数を減らし
- その「意味」を Domain / UseCase 側で与える
という設計改善を行ったので、その考え方をまとめる。
改善前の実装
DAO にドメインの意味が入り込んでいた
もともと DAO には、次のような Query が定義されていた。
@Query("SELECT * FROM transfer_item WHERE isSourceItem = 1")
fun observeSources(): Flow<List<TransferItemEntity>>
この実装はシンプルで分かりやすいが、次の問題を抱えていた。
- DAO が「Source(振替元)」というドメインの意味を知っている
- 条件が増えるたびに SQL / DAO メソッドが増える
- 将来的に Query が爆発する兆候がある
問題意識
ここで違和感を覚えたのは、次の点だった。
- 「振替元である」というのはドメインの意味
- DAO は本来、**永続化の都合(カラムやインデックス)**だけを知るべき
- 「なぜ isSourceItem = 1 なのか」を DAO が理解しているのは不自然
つまり、
DAO が「意味」を持ち始めているのではないか?
という疑問である。
改善方針
改善の方向性はシンプルに次の 2 点とした。
- SQL の条件を固定せず、パラメータ化する
- 条件の意味づけは Domain / UseCase 側で行う
改善後の実装
DAO:検索軸だけを知る
SQL を次のように変更した。
@Query("SELECT * FROM transfer_item WHERE isSourceItem = :isSource")
fun observeByIsSource(isSource: Boolean): Flow<List<TransferItemEntity>>
この DAO は、
isSourceItemというカラムがある- Boolean で絞り込める
という 検索の軸 しか知らない。
「それが振替元なのか」「どの画面で使うのか」は一切知らない。
Repository / UseCase:意味を与える
class LoadSourceItemsUseCase(
private val repository: TransferItemRepository
) {
fun execute(): Flow<List<SourceItem>> =
repository.observeByIsSource(isSource = true)
.map { it.map { entity -> entity.toDomain() } }
}
ここで初めて、
isSource = trueが- 「振替元を取得する」という ドメインの意味
を持つ。
DAO → Repository → UseCase と層を上がるにつれて、
- 実装寄り → 意味寄り
に責務が移動している。
この設計で得られたメリット
1. SQL / DAO の数が増えにくくなった
- 条件ごとに Query を生やす必要がなくなった
- 将来的に
isActiveなどが増えても拡張しやすい
2. DAO の責務が明確になった
- DAO は 「どうやって取るか」 だけを担当
- 「なぜ取るか」 は Domain / UseCase の責務
3. ドメインの変更に強くなった
- 「振替元」の定義が変わっても
- 修正箇所は UseCase / Repository に閉じる
- SQL や DAO への影響を最小限にできる
おわりに
SQL の条件を固定するか、パラメータ化するかは 一見すると小さな実装差に見える。
しかし、
- どの層が意味を知るべきか
- どこで判断を下すべきか
を意識すると、設計の責務分離が一段クリアになる。
DAO を「賢く」しすぎず、 Domain / UseCase に意味を寄せる。
このバランスは、長く運用するアプリほど効いてくると感じている。