求人や技術ブログで「Clean Architectureベース」という表現をよく見かけます。しかし実際にコードを見ると、チームごとに形はさまざまです。UseCaseがある場合もない場合もあり、Repositoryの役割も異なります。
混乱する理由は簡単です。Clean Architectureは特定のフォルダー構成やクラス一覧ではなく、1つの原則だからです。
今回は、その原則とは何か、そしてiOSでUseCaseとRepositoryとして実装するときに、それぞれ何を担当するのかを整理します。
核心は「依存関係は内側にのみ向ける」
Clean ArchitectureはRobert Martin(Uncle Bob)が整理した概念で、アプリを同心円状のレイヤーとして捉えます。
- 最内層: ドメイン。エンティティとビジネスルール。「このアプリは何をするのか」
- 中間層: UseCase。アプリの動作シナリオ
- 外側: UI、データベース、ネットワーク、フレームワーク
ルールは1つだけです。依存関係は常に外側から内側に向かう。
内側のドメインコードは、外側のUIKit、URLSession、SwiftDataを知ってはいけません。逆に外側のレイヤーは内側を知っていても構いません。これにより、UIフレームワークを置き換えてもサーバーAPIが変わっても、アプリの核心となるルールに手を入れずに済みます。
お気づきのとおり、これはDIP(依存性逆転の原則)をアプリ全体の規模に拡張したものです。「具象ではなく抽象に依存する」をレイヤー単位で適用しています。
Repository:「データの出どころ」を隠す境界
Repositoryはドメインとデータソースの境界です。プロトコルはドメイン側に、実装は外側に置きます。
// ドメイン層 — URLSessionは SwiftData何も知らない
protocol PostRepository {
func fetchPosts(userID: String) async throws -> [Post]
}
// データ層 — 実装は外側に
final class RemotePostRepository: PostRepository {
func fetchPosts(userID: String) async throws -> [Post] {
// URLSession 呼び出し, DTO デコード, Post変換
}
}
ポイントはプロトコルの位置です。インターフェースをドメインが所有するため、ドメインは「投稿を取得できる」ことだけを知り、それがサーバーなのかキャッシュなのかローカルDBなのかは知りません。サーバーのレスポンス形式が変わっても、DTO(Data Transfer Object、サーバーレスポンスを運ぶデータ転送オブジェクト)と実装だけを直せば済みます。テストでは偽のRepositoryを差し込めます。
UseCase:「このアプリがすること」を1つ収める器
UseCaseは、アプリの動作シナリオを1つオブジェクトにしたものです。「フィードを読み込む」「投稿を公開する」といった単位です。
final class LoadFeedUseCase {
private let postRepository: PostRepository
private let blockRepository: BlockRepository
func execute(userID: String) async throws -> [Post] {
let posts = try await postRepository.fetchPosts(userID: userID)
let blocked = try await blockRepository.blockedUserIDs()
return posts
.filter { !blocked.contains($0.authorID) }
.sorted { $0.createdAt > $1.createdAt }
}
}
「ブロックしたユーザーの投稿をフィードから除外する」というビジネスルールはUseCaseに置きます。これをViewModelに置くとどうなるでしょうか。フィード、プロフィール、検索の各画面がそれぞれブロックフィルターを実装し、いずれどこかでずれます。UseCaseに切り出せば、ルールを1か所に集約でき、画面なしでテストできます。
そのため、MVVM(Model-View-ViewModel)とClean Architectureは競合する関係ではなく、直交する関係です。MVVMはView側の整理法で、Clean Architectureはその後ろにあるレイヤーの整理法です。前回「Massive ViewModelを避けるにはロジックを下位へ移す」と述べましたが、その移し先がUseCaseとRepositoryです。
どこまで導入すべきでしょうか?
Clean Architectureの落とし穴は過剰導入です。2画面だけのアプリにUseCase・Repository・DTO・Mapperをすべて揃えると、値を1つ渡すだけで5つのファイルを通ります。VIPER(View・Interactor・Presenter・Entity・Router)のボイラープレート問題がそのまま再現されます。
現実的な基準は次のとおりです。
- **Repositoryはほぼ常に効果があります。**ネットワークやDBのコードを画面から分離するだけで、テストと変更が容易になります。
- **複数の画面で共有するビジネスルールが生まれたらUseCaseを導入します。**Repositoryの呼び出しをそのまま渡すだけのUseCaseが大半なら、まだ早いでしょう。
- **レイヤーごとのモデル分離(DTO/ドメイン/画面モデル)は、規模が大きくなってから行います。**最初からすべて分けると、Mapperコードだけが増えていきます。
まとめ
- Clean Architectureはフォルダー構成ではなく、「依存関係は内側(ドメイン)にのみ向ける」という原則です。DIPをアプリ規模に拡張したものです。
- Repositoryはデータの出どころを隠す境界であり、プロトコルをドメインが所有する点が核心です。
- UseCaseは、複数の画面で共有するビジネスルールを置く場所です。MVVMとは直交するため、併用できます。
- すべて揃えることが目標ではありません。まずRepositoryから始め、ルールが増えたらUseCaseを追加しましょう。
次回は方向を変え、状態管理そのものをアーキテクチャにしたTCA(The Composable Architecture)を扱います。

![[iOSアーキテクチャ #6] Clean Architectureの核心のカバー画像](/assets/images/posts/4a332cf7-d161-401b-ab7c-f15324e48609/1.jpg)