Published on

Repository pattern — data qatlami

Authors

Repository Pattern — ViewModel ni ma'lumot manbasining tafsilotlaridan ajratadi. ViewModel "maqolalarni ber" deydi — Repository tarmoqdan mi, keshdan mi, bazadan mi — o'zi hal qiladi.

Repository tuzilmasi

ViewViewModelRepositoryDataSource(lar)
                  ┌─────┴─────┐
                  ▼           ▼
           RemoteDataSource  LocalDataSource
           (API / URLSession) (CoreData / UserDefaults)

To'liq misol

// ═══════════════════════════════════════════════════════════════
//  📦 MODEL — sof ma'lumot
//
//  Model — biznes ob'ekt. Mahsulot haqidagi ma'lumot.
//  Qayerda saqlanganini bilmaydi (server, kesh, bazasi).
//  Bu "entity" deb ham ataladi — domain ning asosi.
// ═══════════════════════════════════════════════════════════════
struct Mahsulot: Identifiable, Codable {
    let id: Int          // Noyob identifikator
    var nomi: String     // Mahsulot nomi
    var narxi: Double    // Narxi (so'm)
    var tavsifi: String  // Tavsif matni
}


// ═══════════════════════════════════════════════════════════════
//  📋 REPOSITORY PROTOKOL — "shartnoma"
//
//  Bu shartnoma ViewModel ga aytadi:
//  "Men senga mahsulotlarni bera olaman, saqlashim mumkin,
//   o'chirishim mumkin. QANDAY qilishim — seni qiziqtirmasin."
//
//  ViewModel FAQAT shu protokolni biladi:
//  - Tarmoqdan kelganmi? — Bilmaydi ✅
//  - Keshdan kelganmi? — Bilmaydi ✅
//  - CoreData danmi? — Bilmaydi ✅
//
//  Bu "Abstraktsiya" — murakkablikni yashirish
// ═══════════════════════════════════════════════════════════════
protocol MahsulotRepositoryProtocol {
    /// Barcha mahsulotlarni olish — manba noma'lum
    func hammasi() async throws -> [Mahsulot]

    /// Bitta mahsulotni ID bo'yicha olish
    func bittasi(id: Int) async throws -> Mahsulot

    /// Yangi mahsulot saqlash yoki mavjudni yangilash
    func saqlash(_ mahsulot: Mahsulot) async throws

    /// Mahsulotni o'chirish
    func ochirish(id: Int) async throws
}


// ═══════════════════════════════════════════════════════════════
//  🔄 HAQIQIY REPOSITORY — tarmoq + lokal kesh
//
//  Bu class — ikki ma'lumot manbasi (DataSource) ni birlashtiradi:
//  1. remoteDataSource — API (server bilan aloqa)
//  2. localDataSource — lokal saqlash (CoreData, UserDefaults)
//
//  Offline-first strategiya:
//  ┌─────────────────────────────────────────┐
//  │ 1. Tarmoqdan yangi ma'lumot SO'RASH     │
//  │ 2. Muvaffaqiyatli → lokal ga SAQLASH    │
//  │ 3. Tarmoq xato → lokal dan O'QISH      │
//  │ 4. Lokal ham bo'sh → XATO ko'rsatish    │
//  └─────────────────────────────────────────┘
//
//  Foydalanuvchi internet bo'lmasa ham ilovani ishlatadi!
// ═══════════════════════════════════════════════════════════════
class MahsulotRepository: MahsulotRepositoryProtocol {
    // DataSource lar — private, tashqaridan ko'rinmaydi
    // Protokol turi — aniq class emas (mock almashish uchun)
    private let remoteDataSource: MahsulotAPIProtocol
    private let localDataSource: MahsulotStorageProtocol

    // Dependency Injection — tashqaridan DataSource uzatish
    // Default qiymatlar bor — oddiy ishlatishda hech narsa yozmaysiz
    // Test da: MockAPI, MockStorage uzatishingiz mumkin
    init(
        remote: MahsulotAPIProtocol = MahsulotAPI(),
        local: MahsulotStorageProtocol = MahsulotStorage()
    ) {
        self.remoteDataSource = remote
        self.localDataSource = local
    }

    // ── HAMMASI — offline-first strategiya ──
    func hammasi() async throws -> [Mahsulot] {
        // Offline-first — foydalanuvchi tajribasi uchun eng yaxshi
        // Internet bo'lsa → yangi ma'lumot
        // Internet bo'lmasa → oxirgi saqlangan ma'lumot

        do {
            // 1-qadam: Tarmoqdan yangi ma'lumot olish
            let tarmoqdan = try await remoteDataSource.mahsulotlarniYukla()

            // 2-qadam: Lokal ga saqlash (keyingi safar uchun kesh)
            // Agar tarmoq ishlamasa — bu kesh dan o'qiymiz
            try await localDataSource.saqlash(tarmoqdan)

            // 3-qadam: Yangi ma'lumotni qaytarish
            return tarmoqdan
        } catch {
            // Tarmoq xatosi! Internet yo'q yoki server ishlamayapti
            // Lokal keshdan o'qishga harakat qilamiz
            let lokal = try await localDataSource.hammasi()

            if lokal.isEmpty {
                // Lokal ham bo'sh — hech qanday ma'lumot yo'q
                // Xatoni yuqoriga uzatamiz — ViewModel hal qiladi
                throw error
            }

            // Lokal da ma'lumot bor — uni ko'rsatamiz
            // Foydalanuvchi eski ma'lumotni ko'radi, lekin ilova ishlamaganidan yaxshi!
            return lokal
        }
    }

    func bittasi(id: Int) async throws -> Mahsulot {
        // Bitta mahsulot — to'g'ridan-to'g'ri tarmoqdan
        try await remoteDataSource.mahsulotniYukla(id: id)
    }

    func saqlash(_ mahsulot: Mahsulot) async throws {
        // Avval serverga saqlash — u "haqiqat manbai"
        try await remoteDataSource.mahsulotniSaqlash(mahsulot)
        // Keyin lokal keshni yangilash
        try await localDataSource.saqlash([mahsulot])
    }

    func ochirish(id: Int) async throws {
        // Avval serverdan o'chirish
        try await remoteDataSource.mahsulotniOchirish(id: id)
        // Keyin lokal dan ham o'chirish — sinxron saqlash
        try await localDataSource.ochirish(id: id)
    }
}


// ═══════════════════════════════════════════════════════════════
//  🌐 DATA SOURCE PROTOKOLLAR
//
//  Har DataSource o'z ishini qiladi:
//  • MahsulotAPIProtocol — faqat tarmoq (URLSession)
//  • MahsulotStorageProtocol — faqat lokal saqlash (CoreData)
//
//  Repository bu ikkalasini birlashtiradi
//  ViewModel faqat Repository ni biladi
// ═══════════════════════════════════════════════════════════════
protocol MahsulotAPIProtocol {
    func mahsulotlarniYukla() async throws -> [Mahsulot]
    func mahsulotniYukla(id: Int) async throws -> Mahsulot
    func mahsulotniSaqlash(_ mahsulot: Mahsulot) async throws
    func mahsulotniOchirish(id: Int) async throws
}

protocol MahsulotStorageProtocol {
    func hammasi() async throws -> [Mahsulot]
    func saqlash(_ mahsulotlar: [Mahsulot]) async throws
    func ochirish(id: Int) async throws
}


// ═══════════════════════════════════════════════════════════════
//  🧠 VIEWMODEL — faqat Repository ni biladi
//
//  ViewModel tarmoq, kesh, bazasi haqida HECH NARSA bilmaydi!
//  U faqat: "repository.hammasi() ber" deydi.
//  Qayerdan kelgani — Repository ning ishi.
//
//  Bu ajratish (separation) test yozishni JUDA osonlashtiradi:
//  Test da MockRepository uzatsak — tarmoq kerak emas!
// ═══════════════════════════════════════════════════════════════
@MainActor
class MahsulotlarViewModel: ObservableObject {
    @Published var mahsulotlar: [Mahsulot] = []
    @Published var yuklanmoqda = false        // ProgressView uchun
    @Published var xatoXabari: String?        // Xato bo'lsa — nil emas

    // Repository — PROTOKOL turi! Aniq class emas!
    // Bu DI (Dependency Injection) ning kaliti
    private let repository: MahsulotRepositoryProtocol

    // Init da repository qabul qilish
    // Default: haqiqiy MahsulotRepository
    // Test da: MockMahsulotRepository uzatiladi
    init(repository: MahsulotRepositoryProtocol = MahsulotRepository()) {
        self.repository = repository
    }

    func yuklash() async {
        yuklanmoqda = true   // Spinner ko'rsatish
        xatoXabari = nil     // Avvalgi xatoni tozalash

        do {
            // ViewModel bilmaydi: tarmoqdan mi, keshdan mi?
            // Repository o'zi hal qiladi!
            mahsulotlar = try await repository.hammasi()
        } catch {
            // Xato — foydalanuvchiga xabar ko'rsatish
            xatoXabari = error.localizedDescription
        }

        yuklanmoqda = false  // Spinner yashirish
    }
}

Repositorysiz vs Repository bilan

Repositorysiz (ViewModel to'g'ridan-to'g'ri tarmoq):
ViewModelURLSessionAPI
ViewModelCoreData
ViewModelUserDefaults
(ViewModel hamma narsani biladi — test qiyin)

Repository bilan:
ViewModelRepository (protokol)
          ┌─────┴─────┐
          API       CoreData
(ViewModel faqat protokolni biladi — test oson)

🎯 Topshiriq: Repository qo'shish

Oldingi MVVM loyihangizga Repository qatlami qo'shing. MaqolaRepositoryProtocol yarating. Haqiqiy va Mock versiyasini yozing. ViewModel ni repository init da qabul qiladigan qilib o'zgartiring. Test da MockRepository ishlatib ViewModel ni tekshiring.

Buy mea coffee