Repository Pattern — iş mantığı ile veri kaynakları arasına bir soyutlama katmanı ekleyen desendir. API, veritabanı veya önbelleği doğrudan çağırmak yerine, Repository veri alma ve depolama için birleşik bir arayüz sağlar. Bu, test etmeyi ve kaynaklar arasında geçiş yapmayı basitleştirir. Daha fazla bilgi için Android Data Layer dokümantasyonuna bakın.
Önemli Noktalar
Repository Pattern, iş mantığını veri kaynaklarına doğrudan erişimden ayıran yapısal bir desendir. Bir Activity, UIViewController veya ViewModel'in doğrudan Retrofit, URLSession, Room veya CoreData'yı çağırması yerine, Repository ile iletişim kurarlar. Repository, verilerin nereden alınacağına — ağ, veritabanı veya önbellek — karar verir ve sonucu birleşik bir formatta döndürür. Bu, tek sorumluluk ilkesini uygular — UI, verilerin nasıl veya nereden alındığını bilmez.
Repository bileşenleri bir arayüz (protokol), bir uygulama ve bir veya daha fazla DataSource içerir. DataSource, tek bir kaynakla çalışan bir sınıftır: RemoteDataSource, HTTP istemcisi aracılığıyla API'yi çağırır, LocalDataSource veritabanına okur ve yazar. Repository, DataSource'ları kurucu (Bağımlılık Enjeksiyonu) aracılığıyla alır ve hangi kaynağı kullanacağına karar verir. Örneğin, bir kullanıcı listesi isterken Repository önce önbelleği, sonra veritabanını, sonra ağı kontrol eder.
Repository Pattern'in faydaları: veri kaynağı değişikliklerinin (API değişiklikleri, DB geçişleri) UI katmanını etkilememesi; Repository veya DataSource'u değiştirerek birim test; UI için şeffaf önbellekleme; ekran mantığını değiştirmeden çevrimiçi ve çevrimdışı modlar arasında geçiş. Android topluluğu, Repository'yi Clean Architecture'da zorunlu bir katman olarak önermektedir.
iOS uygulaması Repository, Swift protokolleri üzerine inşa edilir. Repository protokolü, veri alma ve depolama yöntemlerini bildirir. Gerçek uygulama, başlatıcı aracılığıyla enjekte edilir — bu, testlerde ve SwiftUI önizlemelerinde uygulamanın değiştirilmesine olanak tanır. DataSource'lar da protokol olarak bildirilir: Protocol RemoteDataSource, Protocol LocalDataSource. ViewModel veya Interactor, belirli bir uygulamayı bilmez — yalnızca Repository protokolünü bilir.
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
}
}
Bağımlılık Enjeksiyonu iOS'ta Repository için genellikle bir fabrika veya DI kapsayıcısı (Swinject, Factory) aracılığıyla yapılandırılır. Testlerde, UserRepository protokolü, önceden tanımlanmış verileri döndüren bir mock uygulamasıyla değiştirilir. Async-await, closure'lar ve delegate'ler olmadan kodu senkron ve okunabilir hale getirir. Combine reaktivitesi için Repository yöntemleri, async throws yerine AnyPublisher döndürür.
Android uygulaması Repository, asenkron işlemler için Kotlin Coroutines ve Flow'u yoğun şekilde kullanır. Google, resmi Android mimari kılavuzunda (Android Architecture Components) Repository'yi önermektedir. Repository, kurucu aracılığıyla RemoteDataSource (Retrofit) ve LocalDataSource (Room) alır ve ViewModel, Repository'den bir Flow'a abone olur. Repository, veri stratejisini yönetir: önce önbellek, önce ağ veya her zaman ağ ile önbelleğe yazma.
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 sarmalayıcısı yukarıdaki örnekte Android için standarttır: sealed bir sınıf olan Result, ViewModel'e yükleme durumunu (Yükleniyor, Başarılı, Hata) bildirir. ViewModel, collect aracılığıyla abone olur ve StateFlow veya LiveData'yı günceller. Flow ile Repository, veritabanı değişikliklerini UI'ya otomatik olarak bildirir — bu, manuel yenileme olmadan UI'nin değişikliklerden haberdar olmadığı tek seferlik isteklerden önemli bir farktır.
DataSource — belirli bir veri kaynağıyla çalışmaktan sorumlu sınıflar. RemoteDataSource, API'den veri almak için HTTP istemcisi (URLSession, Retrofit, Ktor) kullanır. LocalDataSource, yerel depolama (CoreData, Realm, Room, UserDefaults, DataStore) ile çalışır. Her DataSource'un dar bir sorumluluğu vardır: RemoteDataSource yalnızca API istek biçimini bilir, LocalDataSource — veritabanı şemasını bilir. Repository, bunları birleştirerek bir önbellekleme stratejisi uygular.
| DataSource | iOS Platformu | Android Platformu | Kaynak |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | Cihazda SQLite |
| Local (önbellek) | NSCache, UserDefaults | DataStore, EncryptedSP | Bellek içi / disk |
| Tercihler | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Ayarlar, tokenlar |
Repository'de önbellekleme stratejileri: Cache-First (önce önbellek, sonra arka plan yükleme), Network-Only (yalnızca ağ, ödeme ekranları için), Network-First-With-Cache-Backup (önce ağ, hata durumunda önbelleğe dönüş). Strateji seçimi senaryoya bağlıdır: ülke listesi uzun süre önbellekte tutulabilir, döviz kurları — 15 dakika, cüzdan bakiyesi — yalnızca ağdan. Repository, stratejiyi uygular ve ViewModel veya UI'yı değiştirmeden bunu değiştirir.
Repository ve Service, örtüşen işlevlere sahip farklı desenlerdir. Repository, veri erişimi ve önbelleklemeden sorumludur, veri modellerini döndürür. Service (veya Interactor, Use Case) iş mantığını içerir: doğrulama, veri dönüşümü, birden çok Repository çağrısını koordine etme. Service, bir siparişi işlemek için UserRepository, OrderRepository ve NotificationRepository'yi birleştirebilir. Repository, iş mantığı içermez — yalnızca CRUD ve önbellekleme.
Repository ne zaman seçilmeli — birden çok kaynakla (API + DB + önbellek) veri navigasyonu, çevrimdışı-ilk mimari, önbellekleme ve şeffaf kaynak değiştirme ihtiyacı. Clean Architecture'da Repository zorunludur ve Google tarafından Android uygulamaları için önerilir. iOS'ta VIPER mimarisinde, Repository'nin rolünü Interactor katmanı üstlenir ve veri erişimi için Manager veya Service ile etkileşime girer.
Service ne zaman yeterli — tek veri kaynağına sahip basit uygulamalar, yazma olmadan salt okunur ekranlar, çevrimdışı modu olmayan projeler. Bu gibi durumlarda, DataSource doğrudan ViewModel veya Presenter tarafından kullanılır ve Repository gereksiz bir katman haline gelir. Ancak, erken aşamada Repository eklemek büyük bir çaba gerektirmez ve gelecekte önbellekleme ve test eklemeyi basitleştirir.
Sıkça Sorulan Sorular
DataSource, tek bir kaynakla (API, DB, önbellek) çalışan bir sınıftır. Repository, birden çok DataSource'u yöneten ve birleşik bir arayüz sağlayan bir sınıftır. Repository, hangi DataSource'un kullanılacağına karar verir ve önbellekleme koordinasyonunu sağlar. DataSource, diğer kaynakların varlığından haberdar değildir; Repository, her kaynağın uygulama ayrıntılarını bilmez.
Evet, Repository SwiftUI'de verileri View'den ayırmak için kullanışlıdır. ViewModel, Repository'deki bir Publisher'a abone olur ve Repository, önbellekleme ve senkronizasyonu yönetir. Basit uygulamalarda, ViewModel içinde doğrudan URLSession kullanılabilir, ancak test edilebilirlik ve ölçeklenebilirlik için Repository tercih edilir. Apple deseni dayatmaz, ancak SwiftData ve Network.framework ile uyumludur.
DataSource'lar, Bağımlılık Enjeksiyonu aracılığıyla mock nesnelerle değiştirilir. Test, bir mock RemoteDataSource (önceden tanımlanmış JSON döndürür) ve bir mock LocalDataSource (verilerin kaydedildiğini doğrular) oluşturur. Repository izole bir şekilde test edilir: önbellekleme stratejisi, hata işleme ve doğru çağrı sırası doğrulanır. Entegrasyon testleri için TestDispatcher (Kotlin) veya MainActor.run (Swift) kullanılır.
Mümkün ancak önerilmez. Protokol olmadan, testlerde ve önizlemelerde uygulamayı değiştirmek imkansızdır. Kotlin'de, Repository arayüzü, DI (Dagger, Hilt, Koin) aracılığıyla uygulamanın değiştirilmesine olanak tanır. Swift'te, async-await ve Combine kodunu test etmek için Repository protokolü zorunludur. İstisna, önbellekleme mantığı olmayan tek veri kaynağına sahip basit projelerdir.
Çevrimdışı-ilk, uygulamanın yerel verileri kullanarak internet olmadan çalıştığı bir stratejidir. Repository anahtar bir rol oynar: önce yerel DataSource'tan veri döndürür, ardından arka planda sunucuyla senkronize olur. Kullanıcı verileri anında görür ve Repository, ağdan yükledikten sonra günceller. Flow ile Room, yerel veritabanındaki veriler değiştiğinde reaktif UI güncellemeleri sağlar.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun