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-এর জন্য মানক: একটি সিল করা ক্লাস 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 ভিন্ন প্যাটার্ন যাদের কার্য overlap হয়। 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 প্রতিটি উৎসের বাস্তবায়ন বিবরণ সম্পর্কে জানে না।
হ্যাঁ, SwiftUI-তে View থেকে ডেটা আলাদা করার জন্য Repository উপযোগী। ViewModel Repository থেকে Publisher-এ সাবস্ক্রাইব করে, এবং Repository ক্যাশিং এবং সিঙ্ক্রোনাইজেশন পরিচালনা করে। সহজ অ্যাপ্লিকেশনে, ViewModel-এ সরাসরি URLSession ব্যবহার করা যেতে পারে, কিন্তু টেস্টযোগ্যতা এবং স্কেলেবিলিটির জন্য Repository পছন্দনীয়। Apple প্যাটার্নটি বাধ্যতামূলক করে না, তবে এটি SwiftData এবং Network.framework-এর সাথে সামঞ্জস্যপূর্ণ।
ডিপেনডেন্সি ইনজেকশনের মাধ্যমে DataSources মক অবজেক্ট দিয়ে প্রতিস্থাপিত হয়। টেস্ট একটি মক RemoteDataSource (পূর্বনির্ধারিত JSON ফেরত দেয়) এবং একটি মক LocalDataSource (ডেটা সংরক্ষিত হয়েছে কিনা যাচাই করে) তৈরি করে। Repository বিচ্ছিন্নভাবে টেস্ট করা হয়: ক্যাশিং কৌশল, ত্রুটি পরিচালনা এবং সঠিক কল ক্রম যাচাই করা হয়। ইন্টিগ্রেশন টেস্টের জন্য TestDispatcher (Kotlin) বা MainActor.run (Swift) ব্যবহার করা হয়।
সম্ভব কিন্তু সুপারিশ করা হয় না। প্রোটোকল ছাড়া, টেস্ট এবং প্রিভিউতে বাস্তবায়ন প্রতিস্থাপন করা অসম্ভব। Kotlin-এ, Repository ইন্টারফেস DI (Dagger, Hilt, Koin) এর মাধ্যমে বাস্তবায়ন প্রতিস্থাপনের অনুমতি দেয়। Swift-এ, async-await এবং Combine কোড টেস্ট করার জন্য Repository প্রোটোকল বাধ্যতামূলক। ব্যতিক্রম হল সহজ প্রজেক্ট যেখানে একক ডেটা উৎস থাকে এবং Repository-তে ক্যাশিং লজিক নেই।
Offline-first একটি কৌশল যেখানে অ্যাপ্লিকেশন স্থানীয় ডেটা ব্যবহার করে ইন্টারনেট ছাড়া কাজ করে। Repository মূল ভূমিকা পালন করে: এটি প্রথমে স্থানীয় DataSource থেকে ডেটা ফেরত দেয়, তারপর ব্যাকগ্রাউন্ডে সার্ভারের সাথে সিঙ্ক্রোনাইজ করে। ব্যবহারকারী তাৎক্ষণিকভাবে ডেটা দেখে, এবং Repository নেটওয়ার্ক থেকে লোড করার পরে এটি আপডেট করে। Flow-সহ Room স্থানীয় ডেটাবেসে ডেটা পরিবর্তন হলে রিয়েক্টিভ UI আপডেট প্রদান করে।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন