Repository Pattern — ビジネスロジックとデータソースの間に抽象化レイヤーを追加するパターンです。API、データベース、キャッシュを直接呼び出す代わりに、Repositoryはデータの取得と保存のための統一インターフェースを提供します。これにより、テストとソースの切り替えが簡素化されます。詳細はAndroid Data Layerのドキュメントをご覧ください。
重要なポイント
Repository Patternは、ビジネスロジックをデータソースへの直接アクセスから隔離する構造パターンです。Activity、UIViewController、ViewModelが直接Retrofit、URLSession、Room、CoreDataを呼び出す代わりに、Repositoryと通信します。Repositoryはデータをネットワーク、データベース、キャッシュのどこから取得するかを決定し、統一された形式で結果を返します。これは単一責任の原則を実装しています — UIはデータがどのように、どこから取得されたかを知りません。
Repositoryのコンポーネントには、インターフェース(プロトコル)、実装、1つ以上のDataSourceが含まれます。DataSourceは単一のソースを扱うクラスです: RemoteDataSourceはHTTPクライアントを介してAPIを呼び出し、LocalDataSourceはデータベースの読み書きを行います。Repositoryはコンストラクタ(依存性注入)を介してDataSourceを受け取り、使用するソースを決定します。たとえば、ユーザー一覧を要求する場合、Repositoryは最初にキャッシュ、次にデータベース、次にネットワークを確認します。
Repository Patternの利点: データソースの変更(API変更、DB移行)がUIレイヤーに影響しないこと、RepositoryまたはDataSourceの置き換えによる単体テスト、UIに対して透過的なキャッシュ、画面ロジックを変更せずにオンラインとオフラインモードを切り替え可能。AndroidコミュニティはClean ArchitectureにおいてRepositoryを必須レイヤーとして推奨しています。
iOSの実装におけるRepositoryはSwiftのプロトコルに基づいています。Repositoryプロトコルはデータの取得と保存のメソッドを宣言します。実際の実装はイニシャライザを介して注入されます — これにより、テストやSwiftUIプレビューでの実装の置き換えが可能になります。DataSourceもプロトコルとして宣言されます: Protocol RemoteDataSource、Protocol LocalDataSource。ViewModelやInteractorは特定の実装を知りません — Repositoryプロトコルのみを知っています。
protocol UserRepository {
func getUsers() async throws -> [User]
}
protocol UserRemoteDataSource {
func fetchUsers() async throws -> [User]
}
protocol UserLocalDataSource {
func getCachedUsers() throws -> [User]
func saveUsers(_: [User]) throws
}
final class UserRepositoryImpl: UserRepository {
private let remote: UserRemoteDataSource
private let local: UserLocalDataSource
init(remote: UserRemoteDataSource, local: UserLocalDataSource) {
self.remote = remote
self.local = local
}
func getUsers() async throws -> [User] {
if let cached = try? local.getCachedUsers() {
return cached
}
let users = try await remote.fetchUsers()
try local.saveUsers(users)
return users
}
}
依存性注入はiOSのRepositoryでは通常、ファクトリまたはDIコンテナ(Swinject、Factory)を介して構成されます。テストでは、UserRepositoryプロトコルは事前定義されたデータを返すモック実装に置き換えられます。Async-awaitにより、クロージャやデリゲートなしで同期的で読みやすいコードになります。Combineのリアクティビティには、Repositoryのメソッドはasync throwsの代わりにAnyPublisherを返します。
Androidの実装におけるRepositoryは、非同期操作にKotlin CoroutinesとFlowを広く利用しています。Googleは公式のAndroidアーキテクチャガイド(Android Architecture Components)でRepositoryを推奨しています。Repositoryはコンストラクタを介してRemoteDataSource(Retrofit)とLocalDataSource(Room)を受け取り、ViewModelはRepositoryからのFlowを購読します。Repositoryはデータ戦略を管理します: キャッシュファースト、ネットワークファースト、または常にネットワークでキャッシュへの書き込み。
interface UserRepository {
fun getUsers(): Flow<Result<List<User>>>
}
interface UserRemoteDataSource {
suspend fun fetchUsers(): List<User>
}
interface UserLocalDataSource {
fun getCachedUsers(): Flow<List<User>>
suspend fun saveUsers(users: List<User>)
}
class UserRepositoryImpl(
private val remote: UserRemoteDataSource,
private val local: UserLocalDataSource
) : UserRepository {
override fun getUsers(): Flow<Result<List<User>>> = flow {
emit(Result.Loading)
local.getCachedUsers().collect { cached ->
if (cached.isNotEmpty()) {
emit(Result.Success(cached))
}
}
try {
val users = remote.fetchUsers()
local.saveUsers(users)
emit(Result.Success(users))
} catch (e: Exception) {
emit(Result.Error(e))
}
}
}
Resultラッパーは上記の例ではAndroidの標準です: シールドクラスResultがViewModelにローディング状態(Loading、Success、Error)を通知します。ViewModelはcollectを介して購読し、StateFlowまたはLiveDataを更新します。Flowを使用したRepositoryはデータベースの変更をUIに自動的に通知します — これは手動更新なしではUIが変更を認識できない単発リクエストとの重要な違いです。
DataSource — 特定のデータソースを扱うクラスです。RemoteDataSourceはHTTPクライアント(URLSession、Retrofit、Ktor)を使用してAPIからデータを取得します。LocalDataSourceはローカルストレージ(CoreData、Realm、Room、UserDefaults、DataStore)を扱います。各DataSourceは狭い責任を持ちます: RemoteDataSourceはAPIリクエスト形式のみを知り、LocalDataSourceはデータベーススキーマのみを知ります。Repositoryはこれらを組み合わせ、キャッシュ戦略を実装します。
| DataSource | iOSプラットフォーム | Androidプラットフォーム | ソース |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | デバイス上のSQLite |
| Local (キャッシュ) | NSCache, UserDefaults | DataStore, EncryptedSP | インメモリ / ディスク |
| 設定 | UserDefaults, Keychain | SharedPreferences, EncryptedSP | 設定、トークン |
Repositoryのキャッシュ戦略: Cache-First(最初にキャッシュ、次にバックグラウンドロード)、Network-Only(ネットワークのみ、支払い画面用)、Network-First-With-Cache-Backup(最初にネットワーク、エラー時にキャッシュにフォールバック)。戦略の選択はシナリオに依存します: 国のリストは長期間キャッシュ可能、為替レートは15分間、ウォレット残高はネットワークのみ。Repositoryは戦略を実装し、ViewModelやUIを変更せずにそれを変更します。
RepositoryとServiceは機能が重複する異なるパターンです。Repositoryはデータアクセスとキャッシュを担当し、データモデルを返します。Service(またはInteractor、Use Case)はビジネスロジックを含みます: 検証、データ変換、複数のRepository呼び出しのオーケストレーション。Serviceは注文を処理するためにUserRepository、OrderRepository、NotificationRepositoryを組み合わせることができます。Repositoryはビジネスロジックを含まず、CRUDとキャッシュのみを担当します。
Repositoryを選ぶべき時 — 複数のソース(API + DB + キャッシュ)を用いたデータナビゲーション、オフラインファーストアーキテクチャ、キャッシュと透過的なソース切り替えの必要性。Clean ArchitectureではRepositoryが必須であり、GoogleはAndroidアプリケーションに推奨しています。iOSのVIPERアーキテクチャでは、Repositoryの役割はInteractorレイヤーが担い、データアクセスのためにManagerやServiceと連携します。
Serviceで十分な時 — 単一のデータソースを持つシンプルなアプリケーション、書き込みのない読み取り専用画面、オフラインモードのないプロジェクト。そのような場合、DataSourceはViewModelまたはPresenterによって直接使用され、Repositoryは不要なレイヤーになります。ただし、早期にRepositoryを追加しても大きな労力は必要なく、将来的なキャッシュ追加やテストが容易になります。
よくある質問
DataSourceは単一のソース(API、DB、キャッシュ)を扱うクラスです。Repositoryは複数のDataSourceを管理し、統一インターフェースを提供するクラスです。RepositoryはどのDataSourceを使用するかを決定し、キャッシュを調整します。DataSourceは他のソースの存在を知りません。Repositoryは各ソースの実装詳細を知りません。
はい、RepositoryはSwiftUIにおいてデータをViewから分離するために有用です。ViewModelはRepositoryからのPublisherを購読し、Repositoryはキャッシュと同期を管理します。シンプルなアプリケーションではViewModel内で直接URLSessionを使用できますが、テスト容易性とスケーラビリティの点でRepositoryが推奨されます。Appleはパターンを強制していませんが、SwiftDataやNetwork.frameworkとの互換性があります。
DataSourceは依存性注入を介してモックオブジェクトに置き換えられます。テストはモックRemoteDataSource(事前定義されたJSONを返す)とモックLocalDataSource(データが保存されたことを確認)を作成します。Repositoryは分離してテストされます: キャッシュ戦略、エラー処理、正しい呼び出し順序が検証されます。統合テストにはTestDispatcher(Kotlin)またはMainActor.run(Swift)が使用されます。
可能ですが推奨されません。プロトコルがないと、テストやプレビューでの実装の置き換えが不可能です。Kotlinでは、RepositoryインターフェースによりDI(Dagger、Hilt、Koin)を介した実装の置き換えが可能です。Swiftでは、async-awaitおよびCombineコードのテストにRepositoryプロトコルが必須です。例外は、キャッシュロジックのない単一データソースのシンプルなプロジェクトです。
Offline-firstは、アプリケーションがローカルデータを使用してインターネットなしで動作する戦略です。Repositoryが重要な役割を果たします: 最初にローカルDataSourceからデータを返し、その後バックグラウンドでサーバーと同期します。ユーザーはデータを即座に表示し、Repositoryはネットワークからの読み込み後にデータを更新します。Flowを使用したRoomは、ローカルデータベースのデータ変更時にリアクティブなUI更新を提供します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。