Repository Pattern — ایک نمونہ جو کاروباری منطق اور ڈیٹا کے ذرائع کے درمیان تجرید کی ایک پرت شامل کرتا ہے۔ API، ڈیٹابیس یا کیشے کو براہ راست کال کرنے کے بجائے، Repository ڈیٹا حاصل کرنے اور ذخیرہ کرنے کے لیے ایک متحد انٹرفیس فراہم کرتا ہے۔ یہ جانچ اور ذرائع کے درمیان سوئچ کرنے کو آسان بناتا ہے۔ مزید جاننے کے لیے Android Data Layer دستاویزات دیکھیں۔
اہم نکات
Repository Pattern ایک ساختی نمونہ ہے جو کاروباری منطق کو ڈیٹا کے ذرائع تک براہ راست رسائی سے الگ کرتا ہے۔ Activity، UIViewController یا ViewModel کے براہ راست Retrofit، URLSession، Room یا CoreData کو کال کرنے کے بجائے، وہ Repository کے ساتھ رابطہ کرتے ہیں۔ Repository فیصلہ کرتا ہے کہ ڈیٹا کہاں سے لینا ہے — نیٹ ورک، ڈیٹابیس یا کیشے سے — اور نتیجہ ایک متحد فارمیٹ میں واپس کرتا ہے۔ یہ واحد ذمہ داری کے اصول کو نافذ کرتا ہے — UI نہیں جانتا کہ ڈیٹا کیسے یا کہاں سے حاصل کیا گیا۔
Repository کے اجزاء میں ایک انٹرفیس (پروٹوکول)، ایک نفاذ اور ایک یا زیادہ DataSources شامل ہیں۔ DataSource ایک کلاس ہے جو ایک ہی ذریعہ کے ساتھ کام کرتی ہے: RemoteDataSource HTTP کلائنٹ کے ذریعے API کو کال کرتا ہے، LocalDataSource ڈیٹابیس میں پڑھتا اور لکھتا ہے۔ Repository کنسٹرکٹر (ڈیپنڈنسی انجیکشن) کے ذریعے DataSources وصول کرتا ہے اور فیصلہ کرتا ہے کہ کون سا ذریعہ استعمال کرنا ہے۔ مثال کے طور پر، صارفین کی فہرست کی درخواست کرتے وقت، Repository پہلے کیشے، پھر ڈیٹابیس، پھر نیٹ ورک چیک کرتا ہے۔
Repository Pattern کے فوائد: ڈیٹا کے ذرائع میں تبدیلیاں (API تبدیلیاں، DB منتقلی) UI پرت کو متاثر نہیں کرتیں؛ Repository یا DataSource کو تبدیل کرکے یونٹ ٹیسٹنگ؛ UI کے لیے کیشنگ شفاف ہے؛ اسکرین منطق کو تبدیل کیے بغیر آن لائن اور آف لائن موڈز کے درمیان سوئچ کرنا۔ Android کمیونٹی Clean Architecture میں Repository کو لازمی پرت کے طور پر تجویز کرتی ہے۔
iOS نفاذ Repository Swift پروٹوکول پر مبنی ہے۔ Repository پروٹوکول ڈیٹا حاصل کرنے اور ذخیرہ کرنے کے طریقوں کا اعلان کرتا ہے۔ حقیقی نفاذ ابتداکار کے ذریعے انجیکٹ کیا جاتا ہے — یہ ٹیسٹ اور SwiftUI پیش نظاروں میں نفاذ کو تبدیل کرنے کی اجازت دیتا ہے۔ DataSources بھی پروٹوکول کے طور پر اعلان کیے جاتے ہیں: 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 closures اور delegates کے بغیر کوڈ کو ہم آہنگ اور پڑھنے کے قابل بناتا ہے۔ 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 کے لیے معیاری ہے: ایک sealed کلاس Result ViewModel کو لوڈنگ کی حالت (لوڈ ہو رہا ہے، کامیاب، خرابی) سے آگاہ کرتی ہے۔ ViewModel collect کے ذریعے سبسکرائب کرتی ہے اور StateFlow یا LiveData کو اپ ڈیٹ کرتی ہے۔ Flow کے ساتھ Repository خود بخود UI کو ڈیٹابیس میں تبدیلیوں سے آگاہ کرتا ہے — یہ ایک بار کی درخواستوں سے کلیدی فرق ہے جہاں UI دستی ریفریش کے بغیر تبدیلیوں سے آگاہ نہیں ہوتا۔
DataSource — کلاسیں جو کسی مخصوص ڈیٹا سورس کے ساتھ کام کرنے کی ذمہ دار ہیں۔ RemoteDataSource API سے ڈیٹا حاصل کرنے کے لیے HTTP کلائنٹ (URLSession، Retrofit، Ktor) استعمال کرتا ہے۔ 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 ایک کلاس ہے جو متعدد DataSources کا انتظام کرتی ہے اور ایک متحد انٹرفیس فراہم کرتی ہے۔ 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 پروٹوکول لازمی ہے۔ مستثنیٰ سادہ پروجیکٹ ہیں جن میں ایک ہی ڈیٹا سورس ہوتا ہے جہاں Repository میں کیشنگ منطق نہیں ہوتی۔
آف لائن-فرسٹ ایک حکمت عملی ہے جہاں ایپلیکیشن مقامی ڈیٹا استعمال کرکے انٹرنیٹ کے بغیر کام کرتی ہے۔ Repository کلیدی کردار ادا کرتا ہے: یہ پہلے مقامی DataSource سے ڈیٹا واپس کرتا ہے، پھر پس منظر میں سرور کے ساتھ ہم آہنگ ہوتا ہے۔ صارف فوری طور پر ڈیٹا دیکھتا ہے، اور Repository نیٹ ورک سے لوڈ کرنے کے بعد اسے اپ ڈیٹ کرتا ہے۔ Flow کے ساتھ Room مقامی ڈیٹابیس میں ڈیٹا تبدیل ہونے پر ردعمل UI اپ ڈیٹ فراہم کرتا ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں