Repository Pattern — pola yang menambahkan lapisan abstraksi antara logika bisnis dan sumber data. Alih-alih panggilan langsung ke API, basis data, atau cache, Repository menyediakan antarmuka terpadu untuk mengambil dan menyimpan data. Ini menyederhanakan pengujian dan peralihan antar sumber. Lebih lanjut di dokumentasi Android Data Layer.
Poin Utama
Repository Pattern — pola struktural yang mengisolasi logika bisnis dari akses langsung ke sumber data. Alih-alih Activity, UIViewController, atau ViewModel memanggil langsung Retrofit, URLSession, Room, atau CoreData, mereka merujuk ke Repository. Repository memutuskan dari mana mengambil data: dari jaringan, basis data, atau cache, dan mengembalikan hasil dalam format terpadu. Ini adalah implementasi prinsip tanggung jawab tunggal — UI tidak tahu bagaimana dan dari mana data diperoleh.
Komponen Repository mencakup antarmuka (protocol), implementasi, dan satu atau lebih DataSource. DataSource — kelas yang bekerja dengan satu sumber: RemoteDataSource memanggil API melalui klien HTTP, LocalDataSource membaca dan menulis ke basis data. Repository menerima DataSource melalui konstruktor (Dependency Injection) dan memilih sumber mana yang akan digunakan. Misalnya, saat meminta daftar pengguna, Repository pertama memeriksa cache, lalu basis data, lalu jaringan.
Keuntungan Repository Pattern: isolasi perubahan sumber data (perubahan API, migrasi DB) tidak memengaruhi lapisan UI; pengujian unit melalui penggantian Repository atau DataSource; caching transparan untuk UI; peralihan antara mode online dan offline tanpa mengubah logika layar. Komunitas Android merekomendasikan Repository sebagai lapisan wajib dalam Clean Architecture.
Implementasi iOS Repository dibangun di atas protokol Swift. Protokol Repository mendeklarasikan metode untuk mengambil dan menyimpan data. Implementasi aktual disuntikkan melalui inisialisasi — ini memungkinkan penggantian implementasi dalam pengujian dan preview SwiftUI. DataSource juga dideklarasikan dengan protokol: Protocol RemoteDataSource, Protocol LocalDataSource. ViewModel atau Interactor tidak tahu implementasi konkret — hanya tahu 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 di iOS untuk Repository biasanya dikonfigurasi melalui pabrik atau kontainer DI (Swinject, Factory). Dalam pengujian, protokol UserRepository diganti dengan implementasi mock yang mengembalikan data yang telah ditentukan. Async-await membuat kode sinkron dan mudah dibaca tanpa closure dan delegate. Untuk reaktivitas Combine, metode Repository mengembalikan AnyPublisher alih-alih async throws.
Implementasi Android Repository banyak menggunakan Kotlin Coroutines dan Flow untuk pekerjaan asinkron. Google merekomendasikan Repository dalam panduan resmi arsitektur Android (Android Architecture Components). Repository menerima RemoteDataSource (Retrofit) dan LocalDataSource (Room) melalui konstruktor, dan ViewModel berlangganan ke Flow dari Repository. Repository mengelola strategi data: pertama cache, lalu jaringan, atau selalu jaringan dengan menulis ke cache.
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))
}
}
}
Pembungkus Result dalam contoh di atas adalah standar untuk Android: sealed class Result memberi tahu ViewModel tentang status pemuatan (Loading, Success, Error). ViewModel berlangganan melalui collect dan memperbarui StateFlow atau LiveData. Repository dengan Flow secara otomatis memberi tahu UI tentang perubahan di basis data — ini adalah perbedaan utama dari kueri satu kali di mana UI tidak tahu tentang perubahan tanpa penyegaran manual.
DataSource — kelas yang bertanggung jawab untuk bekerja dengan sumber data tertentu. RemoteDataSource menggunakan klien HTTP (URLSession, Retrofit, Ktor) untuk mengambil data dari API. LocalDataSource bekerja dengan penyimpanan lokal (CoreData, Realm, Room, UserDefaults, DataStore). Setiap DataSource memiliki tanggung jawab terbatas: RemoteDataSource hanya tahu format permintaan API, LocalDataSource — skema basis data. Repository menggabungkannya, mengimplementasikan strategi caching.
| DataSource | Platform iOS | Platform Android | Sumber |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | REST / GraphQL API |
| Local (DB) | CoreData, SwiftData | Room, SQLDelight | SQLite di perangkat |
| Local (cache) | NSCache, UserDefaults | DataStore, EncryptedSP | In-memory / disk |
| Preferensi | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Pengaturan, token |
Strategi caching di Repository: Cache-First (pertama cache, lalu pemuatan latar belakang), Network-Only (hanya jaringan, untuk layar pembayaran), Network-First-With-Cache-Backup (pertama jaringan, jika error — cache). Pemilihan strategi tergantung pada skenario: daftar negara dapat di-cache untuk waktu lama, nilai tukar — 15 menit, saldo dompet — hanya dari jaringan. Repository mengimplementasikan strategi dan mengubahnya tanpa memodifikasi ViewModel atau UI.
Repository dan Service — pola berbeda dengan fungsi yang tumpang tindih. Repository bertanggung jawab atas akses data dan caching, mengembalikan model data. Service (atau Interactor, Use Case) berisi logika bisnis: validasi, transformasi data, orkestrasi panggilan ke beberapa Repository. Service dapat menggabungkan UserRepository, OrderRepository, dan NotificationRepository untuk memproses pesanan. Repository tidak berisi logika bisnis — hanya CRUD dan caching.
Kapan memilih Repository — navigasi data dengan banyak sumber (API + DB + cache), arsitektur offline-first, kebutuhan caching dan peralihan transparan antar sumber. Repository wajib dalam Clean Architecture dan direkomendasikan oleh Google untuk aplikasi Android. Dalam arsitektur VIPER di iOS, peran Repository dilakukan oleh lapisan Interactor, yang berinteraksi dengan Manager atau Service untuk akses data.
Kapan Service cukup — aplikasi sederhana dengan satu sumber data, layar hanya-baca tanpa penulisan, proyek tanpa mode offline. Dalam kasus seperti itu, DataSource digunakan langsung oleh ViewModel atau Presenter, dan Repository menjadi lapisan yang berlebihan. Namun, menambahkan Repository di tahap awal tidak memerlukan biaya besar dan memudahkan penambahan caching dan pengujian di masa depan.
Pertanyaan yang Sering Diajukan
DataSource — kelas yang bekerja dengan satu sumber (API, DB, cache). Repository — kelas yang mengelola beberapa DataSource dan menyediakan antarmuka terpadu. Repository memutuskan dari DataSource mana mengambil data dan mengoordinasikan caching. DataSource tidak tahu tentang keberadaan sumber lain, Repository tidak tahu detail implementasi setiap sumber.
Ya, Repository berguna di SwiftUI untuk memisahkan data dari View. ViewModel berlangganan Publisher dari Repository, dan Repository mengelola caching dan sinkronisasi. Di aplikasi sederhana, URLSession dapat digunakan langsung di ViewModel, tetapi untuk testabilitas dan skalabilitas, Repository lebih disukai. Apple tidak memaksakan pola ini, tetapi kompatibel dengan SwiftData dan Network.framework.
DataSource diganti dengan objek mock melalui Dependency Injection. Pengujian membuat mock RemoteDataSource (mengembalikan JSON yang telah ditentukan) dan mock LocalDataSource (memeriksa apakah data telah disimpan). Repository diuji secara terisolasi: strategi caching, penanganan kesalahan, dan urutan panggilan yang benar diperiksa. Untuk pengujian integrasi, digunakan TestDispatcher (Kotlin) atau MainActor.run (Swift).
Bisa, tetapi tidak disarankan. Tanpa protokol, implementasi tidak dapat diganti dalam pengujian dan preview. Di Kotlin, antarmuka Repository memungkinkan penggantian implementasi melalui DI (Dagger, Hilt, Koin). Di Swift, protokol Repository wajib untuk menguji kode async-await dan Combine. Pengecualian — proyek sederhana dengan satu sumber data di mana Repository tidak berisi logika caching.
Offline-first — strategi di mana aplikasi bekerja tanpa internet menggunakan data lokal. Repository memainkan peran kunci: pertama mengembalikan data dari DataSource lokal, kemudian sinkronisasi dengan server di latar belakang. Pengguna melihat data secara instan, dan Repository memperbaruinya setelah memuat dari jaringan. Room dengan Flow menyediakan pembaruan UI reaktif saat data berubah di basis data lokal.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga