Repository Pattern — vzor, který přidává vrstvu abstrakce mezi obchodní logiku a zdroje dat. Místo přímých volání API, databáze nebo mezipaměti Repository poskytuje jednotné rozhraní pro získávání a ukládání dat. To zjednodušuje testování a přepínání mezi zdroji. Více v dokumentaci Android Data Layer.
Hlavní
Repository Pattern — strukturální vzor, který izoluje obchodní logiku od přímého přístupu ke zdrojům dat. Místo toho, aby Activity, UIViewController nebo ViewModel přímo volaly Retrofit, URLSession, Room nebo CoreData, obracejí se na Repository. Repository rozhoduje, odkud data vzít: ze sítě, databáze nebo mezipaměti, a vrací výsledek v jednotném formátu. To je implementace principu jediné odpovědnosti — UI neví, jak a odkud byla data získána.
Komponenty Repository zahrnují rozhraní (protocol), implementaci a jeden nebo více DataSource. DataSource — třída pracující s jedním zdrojem: RemoteDataSource volá API přes HTTP klienta, LocalDataSource čte a zapisuje do databáze. Repository přijímá DataSource přes konstruktor (Dependency Injection) a vybírá, ke kterému zdroji se obrátit. Například při požadavku na seznam uživatelů Repository nejprve zkontroluje mezipaměť, poté databázi, pak síť.
Výhody Repository Pattern: izolace změn zdrojů dat (změna API, migrace DB) neovlivňuje UI vrstvu; unit testování pomocí náhrady Repository nebo DataSource; ukládání do mezipaměti transparentní pro UI; přepínání mezi online a offline režimem bez změny logiky obrazovky. Komunita Android doporučuje Repository jako povinnou vrstvu v Clean Architecture.
iOS implementace Repository je postavena na Swift prototypech. Protokol Repository deklaruje metody pro získávání a ukládání dat. Skutečná implementace je vložena přes inicializátor — to umožňuje nahrazení implementace v testech a SwiftUI preview. DataSource jsou také deklarovány protokoly: Protocol RemoteDataSource, Protocol LocalDataSource. ViewModel nebo Interactor nezná konkrétní implementaci — zná pouze protokol 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
}
}
Dependency Injection v iOS pro Repository se obvykle konfiguruje přes továrnu nebo DI kontejner (Swinject, Factory). V testech je protokol UserRepository nahrazen mock implementací vracející předdefinovaná data. Async-await činí kód synchronním a čitelným bez uzávěrů a delegátů. Pro reaktivitu Combine metody Repository vracejí AnyPublisher místo async throws.
Android implementace Repository široce využívá Kotlin Coroutines a Flow pro asynchronní práci. Google doporučuje Repository v oficiálním průvodci architekturou Android (Android Architecture Components). Repository přijímá RemoteDataSource (Retrofit) a LocalDataSource (Room) přes konstruktor a ViewModel se přihlašuje k odběru Flow z Repository. Repository spravuje strategii dat: nejprve mezipaměť, poté síť nebo vždy síť se zápisem do mezipaměti.
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 obal v uvedeném příkladu je standardní pro Android: sealed class Result informuje ViewModel o stavu načítání (Loading, Success, Error). ViewModel se přihlašuje přes collect a aktualizuje StateFlow nebo LiveData. Repository s Flow automaticky upozorňuje UI na změny v databázi — to je klíčový rozdíl oproti jednorázovým dotazům, kde se UI o změnách nedozví bez ručního obnovení.
DataSource — třídy odpovědné za práci s konkrétním zdrojem dat. RemoteDataSource používá HTTP klienta (URLSession, Retrofit, Ktor) k získání dat z API. LocalDataSource pracuje s místním úložištěm (CoreData, Realm, Room, UserDefaults, DataStore). Každý DataSource má úzkou odpovědnost: RemoteDataSource zná pouze formát API požadavku, LocalDataSource — schéma databáze. Repository je kombinuje a implementuje strategii ukládání do mezipaměti.
| DataSource | Platforma iOS | Platforma Android | Zdroj |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | SQLite na zařízení |
| Local (mezipaměť) | NSCache, UserDefaults | DataStore, EncryptedSP | V paměti / disk |
| Předvolby | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Nastavení, tokeny |
Strategie ukládání do mezipaměti v Repository: Cache-First (nejprve mezipaměť, poté načítání na pozadí), Network-Only (pouze síť, pro platební obrazovky), Network-First-With-Cache-Backup (nejprve síť, při chybě — mezipaměť). Výběr strategie závisí na scénáři: seznam zemí lze ukládat do mezipaměti dlouho, směnné kurzy — 15 minut, zůstatek peněženky — pouze ze sítě. Repository implementuje strategii a mění ji bez úpravy ViewModel nebo UI.
Repository a Service — odlišné vzory s překrývajícími se funkcemi. Repository zodpovídá za přístup k datům a jejich ukládání do mezipaměti a vrací datové modely. Service (nebo Interactor, Use Case) obsahuje obchodní logiku: validaci, transformaci dat, orchestrování volání více Repository. Service může kombinovat UserRepository, OrderRepository a NotificationRepository pro zpracování objednávky. Repository neobsahuje obchodní logiku — pouze CRUD a ukládání do mezipaměti.
Kdy zvolit Repository — navigace daty s více zdroji (API + DB + mezipaměť), offline-first architektura, potřeba ukládání do mezipaměti a transparentního přepínání zdrojů. Repository je povinné v Clean Architecture a doporučené Googlem pro Android aplikace. V architektuře VIPER na iOS plní roli Repository vrstva Interactor, která komunikuje s Manager nebo Service pro přístup k datům.
Kdy stačí Service — jednoduché aplikace s jedním zdrojem dat, obrazovky pouze pro čtení bez zápisu, projekty bez offline režimu. V takových případech je DataSource používán přímo ViewModel nebo Presenter a Repository se stává nadbytečnou vrstvou. Přidání Repository v rané fázi však nevyžaduje velké náklady a usnadňuje přidání ukládání do mezipaměti a testů v budoucnu.
Často kladené otázky
DataSource — třída pracující s jedním zdrojem (API, DB, mezipaměť). Repository — třída spravující více DataSource a poskytující jednotné rozhraní. Repository rozhoduje, ze kterého DataSource data vzít, a koordinuje ukládání do mezipaměti. DataSource neví o existenci jiných zdrojů, Repository nezná detaily implementace každého zdroje.
Ano, Repository je užitečné ve SwiftUI pro oddělení dat od View. ViewModel se přihlašuje k Publisher z Repository a Repository spravuje ukládání do mezipaměti a synchronizaci. V jednoduchých aplikacích lze použít URLSession přímo ve ViewModel, ale pro testovatelnost a škálovatelnost je Repository vhodnější. Apple tento vzor nevynucuje, ale je kompatibilní se SwiftData a Network.framework.
DataSource jsou nahrazeny mock objekty přes Dependency Injection. Test vytvoří mock RemoteDataSource (vrací předdefinovaný JSON) a mock LocalDataSource (kontroluje, zda byla data uložena). Repository je testováno izolovaně: kontroluje se strategie ukládání do mezipaměti, zpracování chyb a správné pořadí volání. Pro integrační testy se používá TestDispatcher (Kotlin) nebo MainActor.run (Swift).
Lze, ale nedoporučuje se. Bez protokolu nelze nahradit implementaci v testech a preview. V Kotlin rozhraní Repository umožňuje nahrazení implementace přes DI (Dagger, Hilt, Koin). Ve Swift je protokol Repository povinný pro testování async-await a Combine kódu. Výjimka — jednoduché projekty s jedním zdrojem dat, kde Repository neobsahuje logiku ukládání do mezipaměti.
Offline-first — strategie, kdy aplikace funguje bez internetu pomocí lokálních dat. Repository hraje klíčovou roli: nejprve vrací data z lokálního DataSource, poté synchronizuje se serverem na pozadí. Uživatel vidí data okamžitě a Repository je aktualizuje po načtení ze sítě. Room s Flow zajišťuje reaktivní aktualizaci UI při změně dat v lokální databázi.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také