Repository Pattern — egy minta, amely absztrakciós réteget ad az üzleti logika és az adatforrások közé. A közvetlen API-, adatbázis- vagy gyorsítótár-hívások helyett a Repository egységes felületet biztosít az adatok lekéréséhez és mentéséhez. Ez leegyszerűsíti a tesztelést és a források közötti váltást. Bővebben a Android Data Layer dokumentációban.
Lényeg
Repository Pattern — egy strukturális minta, amely elkülöníti az üzleti logikát az adatforrásokhoz való közvetlen hozzáféréstől. Ahelyett, hogy az Activity, UIViewController vagy ViewModel közvetlenül hívná a Retrofit-ot, URLSession-t, Room-ot vagy CoreData-t, a Repository-hoz fordulnak. A Repository dönti el, honnan vegye az adatokat: a hálózatból, adatbázisból vagy gyorsítótárból, és egységes formátumban adja vissza az eredményt. Ez az egyszeri felelősség elvének megvalósítása — az UI nem tudja, hogyan és honnan szerezték be az adatokat.
A Repository összetevői tartalmazzák az interfészt (protocol), a megvalósítást és egy vagy több DataSource-t. DataSource — egy osztály, amely egy forrással dolgozik: a RemoteDataSource API-t hív HTTP-kliensen keresztül, a LocalDataSource adatbázisba olvas és ír. A Repository a DataSource-okat a konstruktoron keresztül kapja (Dependency Injection), és eldönti, melyik forráshoz forduljon. Például egy felhasználói lista kérésénél a Repository először a gyorsítótárat, majd az adatbázist, végül a hálózatot ellenőrzi.
Előnyei a Repository Pattern-nek: az adatforrások változásainak elkülönítése (API-váltás, DB migráció) nem érinti az UI réteget; egységtesztelés a Repository vagy DataSource helyettesítésével; gyorsítótárazás átlátható az UI számára; váltás online és offline mód között a képernyő logikájának megváltoztatása nélkül. Az Android közösség a Repository-t kötelező rétegként ajánlja a Clean Architecture-ben.
iOS implementáció a Repository Swift protokollokra épül. A Repository protokoll deklarálja az adatok lekérésére és mentésére szolgáló metódusokat. A tényleges megvalósítás az inicializálón keresztül kerül befecskendezésre — ez lehetővé teszi a megvalósítás cseréjét tesztekben és SwiftUI előnézetekben. A DataSource-ok is protokollokkal vannak deklarálva: Protocol RemoteDataSource, Protocol LocalDataSource. A ViewModel vagy Interactor nem ismeri a konkrét megvalósítást — csak a Repository protokollt ismeri.
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 iOS-ben a Repository számára általában gyáron vagy DI konténeren (Swinject, Factory) keresztül van konfigurálva. A tesztekben a UserRepository protokoll egy előre meghatározott adatokat visszaadó mock megvalósításra cserélődik. Az async-await szinkronná és olvashatóvá teszi a kódot lezárások és delegate-ek nélkül. A Combine reaktivitáshoz a Repository metódusai AnyPublisher-t adnak vissza az async throws helyett.
Android implementáció a Repository széles körben használja a Kotlin Coroutines és Flow-t aszinkron műveletekhez. A Google a Repository-t ajánlja a hivatalos Android architektúra útmutatóban (Android Architecture Components). A Repository a konstruktoron keresztül kapja a RemoteDataSource-t (Retrofit) és LocalDataSource-t (Room), a ViewModel pedig feliratkozik a Repository Flow-jára. A Repository kezeli az adatstratégiát: először gyorsítótár, majd hálózat, vagy mindig hálózat a gyorsítótárba írással.
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 burkoló a fenti példában Android esetén szabványos: a sealed class Result tájékoztatja a ViewModel-t a betöltési állapotról (Loading, Success, Error). A ViewModel a collect-en keresztül fizet fel, és frissíti a StateFlow-t vagy LiveData-t. A Repository Flow-val automatikusan értesíti az UI-t az adatbázis változásairól — ez a kulcsfontosságú különbség az egyszeri lekérdezésekhez képest, ahol az UI manuális frissítés nélkül nem tud a változásokról.
DataSource — osztályok, amelyek egy adott adatforrással való munkáért felelősek. A RemoteDataSource HTTP-klienst (URLSession, Retrofit, Ktor) használ az adatok API-ból történő lekéréséhez. A LocalDataSource helyi tárolóval (CoreData, Realm, Room, UserDefaults, DataStore) dolgozik. Minden DataSource szűk felelősséggel rendelkezik: a RemoteDataSource csak az API-kérés formátumát ismeri, a LocalDataSource — az adatbázis sémáját. A Repository kombinálja őket, megvalósítva a gyorsítótárazási stratégiát.
| DataSource | iOS platform | Android platform | Forrás |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | SQLite az eszközön |
| Local (gyorsítótár) | NSCache, UserDefaults | DataStore, EncryptedSP | Memóriában / lemezen |
| Preferencia | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Beállítások, tokenek |
Gyorsítótárazási stratégiák a Repository-ban: Cache-First (először gyorsítótár, majd háttérbetöltés), Network-Only (csak hálózat, fizetési képernyőkhöz), Network-First-With-Cache-Backup (először hálózat, hiba esetén — gyorsítótár). A stratégia kiválasztása a forgatókönyvtől függ: az országok listája hosszú ideig gyorsítótárazható, a devizaárfolyamok — 15 percig, a pénztárca egyenlege — csak a hálózatból. A Repository megvalósítja a stratégiát, és anélkül változtatja, hogy módosítaná a ViewModel-t vagy UI-t.
Repository és Service — különböző minták átfedő funkciókkal. A Repository felelős az adatokhoz való hozzáférésért és azok gyorsítótárazásáért, adatmodelleket visszaadva. A Service (vagy Interactor, Use Case) üzleti logikát tartalmaz: validációt, adatátalakítást, több Repository hívásának orkesztrálását. A Service kombinálhatja a UserRepository-t, OrderRepository-t és NotificationRepository-t egy rendelés feldolgozásához. A Repository nem tartalmaz üzleti logikát — csak CRUD-ot és gyorsítótárazást.
Mikor válassza a Repository-t — adatnavigáció több forrással (API + DB + gyorsítótár), offline-first architektúra, gyorsítótárazás és átlátható forrásváltás szükségessége. A Repository kötelező a Clean Architecture-ben, és a Google ajánlja Android alkalmazásokhoz. Az iOS VIPER architektúrában a Repository szerepét az Interactor réteg tölti be, amely Manager vagy Service segítségével fér hozzá az adatokhoz.
Mikor elég a Service — egyszerű alkalmazások egy adatforrással, csak olvasható képernyők írás nélkül, offline mód nélküli projektek. Ilyen esetekben a DataSource-t közvetlenül a ViewModel vagy Presenter használja, a Repository pedig felesleges réteggé válik. A Repository korai szakaszban történő hozzáadása azonban nem igényel nagy költséget, és megkönnyíti a gyorsítótárazás és tesztek jövőbeli hozzáadását.
Gyakran Ismételt Kérdések
DataSource — egy osztály, amely egy forrással dolgozik (API, DB, gyorsítótár). Repository — egy osztály, amely több DataSource-t kezel és egységes felületet biztosít. A Repository dönti el, melyik DataSource-ból vegye az adatokat, és koordinálja a gyorsítótárazást. A DataSource nem tud más források létezéséről, a Repository nem ismeri az egyes források megvalósításának részleteit.
Igen, a Repository hasznos a SwiftUI-ban az adatok View-tól való elkülönítéséhez. A ViewModel feliratkozik a Repository Publisherére, a Repository pedig kezeli a gyorsítótárazást és szinkronizációt. Egyszerű alkalmazásokban a URLSession közvetlenül a ViewModel-ben is használható, de a tesztelhetőség és skálázhatóság érdekében a Repository előnyösebb. Az Apple nem kényszeríti ezt a mintát, de kompatibilis a SwiftData-val és Network.framework-kel.
A DataSource-ok Dependency Injection segítségével mock objektumokra cserélődnek. A teszt létrehoz egy mock RemoteDataSource-t (előre meghatározott JSON-t ad vissza) és egy mock LocalDataSource-t (ellenőrzi, hogy az adatok mentésre kerültek). A Repository elkülönítve kerül tesztelésre: ellenőrzik a gyorsítótárazási stratégiát, hibakezelést és a hívások helyes sorrendjét. Integrációs tesztekhez TestDispatcher (Kotlin) vagy MainActor.run (Swift) használatos.
Lehet, de nem ajánlott. Protokoll nélkül nem cserélhető a megvalósítás tesztekben és előnézetben. Kotlinban a Repository interfész lehetővé teszi a megvalósítás cseréjét DI-n keresztül (Dagger, Hilt, Koin). Swiftben a Repository protokoll kötelező az async-await és Combine kód teszteléséhez. Kivétel — egyszerű projektek egy adatforrással, ahol a Repository nem tartalmaz gyorsítótárazási logikát.
Offline-first — egy stratégia, amelyben az alkalmazás internet nélkül működik, helyi adatokat használva. A Repository kulcsszerepet játszik: először visszaadja az adatokat a helyi DataSource-ból, majd a háttérben szinkronizál a szerverrel. A felhasználó azonnal látja az adatokat, a Repository pedig frissíti azokat a hálózatról való betöltés után. A Room Flow-val biztosítja az UI reaktív frissítését a helyi adatbázis adatváltozásainál.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is