Repository Pattern: nó là gì, mẫu trừu tượng hóa dữ liệu trong iOS và Android

Tác giả: IT Sectr Đã đăng: 2026-02-17 Thời gian đọc: 7 phút

Repository Pattern — một mẫu thêm lớp trừu tượng giữa logic nghiệp vụ và các nguồn dữ liệu. Thay vì gọi trực tiếp API, cơ sở dữ liệu hoặc bộ nhớ đệm, Repository cung cấp một giao diện thống nhất để lấy và lưu trữ dữ liệu. Điều này đơn giản hóa việc kiểm thử và chuyển đổi giữa các nguồn. Đọc thêm trong tài liệu Android Data Layer.

Những điểm chính

  • Repository Pattern — một lớp giữa logic nghiệp vụ và các nguồn dữ liệu (API, DB, bộ nhớ đệm)
  • DataSource — các lớp riêng biệt cho từng nguồn: RemoteDataSource, LocalDataSource
  • Nguồn sự thật duy nhất — Repository trở thành nguồn dữ liệu duy nhất cho lớp UI
  • Kiểm thử — Repository dễ dàng được thay thế bằng đối tượng giả qua DI cho kiểm thử đơn vị
  • Tương thích — hoạt động với MVVM, Clean Architecture và các mẫu kiến trúc khác

Repository Pattern trong phát triển di động là gì?

Repository Pattern là một mẫu cấu trúc cô lập logic nghiệp vụ khỏi truy cập trực tiếp vào các nguồn dữ liệu. Thay vì Activity, UIViewController hoặc ViewModel gọi trực tiếp Retrofit, URLSession, Room hoặc CoreData, chúng giao tiếp với Repository. Repository quyết định lấy dữ liệu từ đâu — từ mạng, cơ sở dữ liệu hay bộ nhớ đệm — và trả về kết quả ở định dạng thống nhất. Điều này thực hiện nguyên tắc trách nhiệm đơn lẻ — UI không biết dữ liệu được lấy như thế nào hay từ đâu.

Các thành phần của Repository bao gồm một giao diện (protocol), một triển khai và một hoặc nhiều DataSource. DataSource là một lớp làm việc với một nguồn duy nhất: RemoteDataSource gọi API qua HTTP client, LocalDataSource đọc và ghi vào cơ sở dữ liệu. Repository nhận DataSources qua hàm khởi tạo (Dependency Injection) và quyết định sử dụng nguồn nào. Ví dụ, khi yêu cầu danh sách người dùng, Repository đầu tiên kiểm tra bộ nhớ đệm, sau đó cơ sở dữ liệu, sau đó mạng.

Lợi ích của Repository Pattern: sự cô lập thay đổi nguồn dữ liệu (thay đổi API, di chuyển DB) không ảnh hưởng đến lớp UI; kiểm thử đơn vị qua việc thay thế Repository hoặc DataSource; lưu trữ đệm trong suốt với UI; chuyển đổi giữa chế độ trực tuyến và ngoại tuyến mà không thay đổi logic màn hình. Cộng đồng Android khuyến nghị Repository như một lớp bắt buộc trong Clean Architecture.

Repository Pattern trong iOS với Swift: triển khai và ví dụ

Triển khai iOS của Repository được xây dựng trên các giao thức Swift. Giao thức Repository khai báo các phương thức để lấy và lưu trữ dữ liệu. Triển khai thực tế được tiêm qua hàm khởi tạo — điều này cho phép thay thế triển khai trong kiểm thử và bản xem trước SwiftUI. DataSources cũng được khai báo dưới dạng giao thức: Protocol RemoteDataSource, Protocol LocalDataSource. ViewModel hoặc Interactor không biết về một triển khai cụ thể — chỉ biết về giao thức Repository.

swift
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 trong iOS cho Repository thường được cấu hình qua factory hoặc DI container (Swinject, Factory). Trong kiểm thử, giao thức UserRepository được thay thế bằng triển khai giả trả về dữ liệu được xác định trước. Async-await làm cho mã đồng bộ và dễ đọc mà không cần closures và delegates. Để phản ứng với Combine, các phương thức Repository trả về AnyPublisher thay vì async throws.

Repository Pattern trong Android với Kotlin: ví dụ với Flow

Triển khai Android của Repository sử dụng rộng rãi Kotlin Coroutines và Flow cho các hoạt động bất đồng bộ. Google khuyến nghị Repository trong hướng dẫn kiến trúc Android chính thức (Android Architecture Components). Repository chấp nhận RemoteDataSource (Retrofit) và LocalDataSource (Room) qua hàm khởi tạo, và ViewModel đăng ký Flow từ Repository. Repository quản lý chiến lược dữ liệu: bộ nhớ đệm trước, mạng trước, hoặc luôn mạng với ghi vào bộ nhớ đệm.

kotlin
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))
        }
    }
}

Wrapper Result trong ví dụ trên là tiêu chuẩn cho Android: một lớp sealed Result thông báo cho ViewModel về trạng thái tải (Đang tải, Thành công, Lỗi). ViewModel đăng ký qua collect và cập nhật StateFlow hoặc LiveData. Repository với Flow tự động thông báo cho UI về các thay đổi trong cơ sở dữ liệu — đây là điểm khác biệt chính so với các yêu cầu một lần nơi UI không biết về thay đổi nếu không làm mới thủ công.

DataSource: Remote, Local và lưu trữ đệm dữ liệu

DataSource — các lớp chịu trách nhiệm làm việc với một nguồn dữ liệu cụ thể. RemoteDataSource sử dụng HTTP client (URLSession, Retrofit, Ktor) để lấy dữ liệu từ API. LocalDataSource làm việc với bộ nhớ cục bộ (CoreData, Realm, Room, UserDefaults, DataStore). Mỗi DataSource có trách nhiệm hẹp: RemoteDataSource chỉ biết về định dạng yêu cầu API, LocalDataSource — về lược đồ cơ sở dữ liệu. Repository kết hợp chúng, triển khai chiến lược lưu trữ đệm.

DataSourceNền tảng iOSNền tảng AndroidNguồn
RemoteURLSession + CodableRetrofit + Moshi/GsonAPI REST / GraphQL
Local (DB)CoreData, SwiftDataRoom, SQLDelightSQLite trên thiết bị
Local (bộ nhớ đệm)NSCache, UserDefaultsDataStore, EncryptedSPTrong bộ nhớ / đĩa
Tùy chọnUserDefaults, KeychainSharedPreferences, EncryptedSPCài đặt, token

Chiến lược lưu trữ đệm trong Repository: Cache-First (bộ nhớ đệm trước, sau đó tải nền), Network-Only (chỉ mạng, cho màn hình thanh toán), Network-First-With-Cache-Backup (mạng trước, dự phòng bộ nhớ đệm khi lỗi). Lựa chọn chiến lược phụ thuộc vào kịch bản: danh sách quốc gia có thể được lưu đệm lâu, tỷ giá hối đoái — trong 15 phút, số dư ví — chỉ từ mạng. Repository triển khai chiến lược và thay đổi nó mà không sửa đổi ViewModel hoặc UI.

Repository Pattern vs Service Layer: khác biệt và lựa chọn

Repository và Service là các mẫu khác nhau với chức năng chồng chéo. Repository chịu trách nhiệm truy cập dữ liệu và lưu trữ đệm, trả về các mô hình dữ liệu. Service (hoặc Interactor, Use Case) chứa logic nghiệp vụ: xác thực, chuyển đổi dữ liệu, điều phối các cuộc gọi đến nhiều Repository. Service có thể kết hợp UserRepository, OrderRepository và NotificationRepository để xử lý đơn hàng. Repository không chứa logic nghiệp vụ — chỉ CRUD và lưu trữ đệm.

Khi nào chọn Repository — điều hướng dữ liệu với nhiều nguồn (API + DB + bộ nhớ đệm), kiến trúc offline-first, nhu cầu lưu trữ đệm và chuyển đổi nguồn trong suốt. Repository là bắt buộc trong Clean Architecture và được Google khuyến nghị cho các ứng dụng Android. Trong kiến trúc VIPER trên iOS, vai trò của Repository được thực hiện bởi lớp Interactor, tương tác với Manager hoặc Service để truy cập dữ liệu.

Khi nào Service đủ — ứng dụng đơn giản với một nguồn dữ liệu duy nhất, màn hình chỉ đọc không ghi, dự án không có chế độ ngoại tuyến. Trong những trường hợp này, DataSource được sử dụng trực tiếp bởi ViewModel hoặc Presenter, và Repository trở thành một lớp dư thừa. Tuy nhiên, thêm Repository ở giai đoạn đầu không đòi hỏi nhiều công sức và đơn giản hóa việc thêm bộ nhớ đệm và kiểm thử trong tương lai.

Câu hỏi thường gặp

Repository khác DataSource như thế nào?

DataSource là một lớp làm việc với một nguồn duy nhất (API, DB, bộ nhớ đệm). Repository là một lớp quản lý nhiều DataSource và cung cấp một giao diện thống nhất. Repository quyết định sử dụng DataSource nào và điều phối lưu trữ đệm. DataSource không biết về sự tồn tại của các nguồn khác; Repository không biết chi tiết triển khai của từng nguồn.

Có cần Repository trong iOS với SwiftUI không?

Có, Repository hữu ích trong SwiftUI để tách dữ liệu khỏi View. ViewModel đăng ký Publisher từ Repository, và Repository quản lý lưu trữ đệm và đồng bộ hóa. Trong ứng dụng đơn giản, có thể sử dụng URLSession trực tiếp trong ViewModel, nhưng để kiểm thử và mở rộng, Repository được ưu tiên. Apple không áp đặt mẫu này, nhưng nó tương thích với SwiftData và Network.framework.

Làm thế nào để kiểm thử Repository với nhiều DataSource?

DataSource được thay thế bằng các đối tượng giả qua Dependency Injection. Kiểm thử tạo RemoteDataSource giả (trả về JSON được xác định trước) và LocalDataSource giả (xác minh dữ liệu được lưu). Repository được kiểm thử độc lập: chiến lược lưu trữ đệm, xử lý lỗi và thứ tự gọi đúng được xác minh. Cho kiểm thử tích hợp, sử dụng TestDispatcher (Kotlin) hoặc MainActor.run (Swift).

Có thể sử dụng Repository mà không có giao diện (protocol) không?

Có thể nhưng không được khuyến nghị. Không có giao thức, không thể thay thế triển khai trong kiểm thử và bản xem trước. Trong Kotlin, giao diện Repository cho phép thay thế triển khai qua DI (Dagger, Hilt, Koin). Trong Swift, giao thức Repository là bắt buộc để kiểm thử mã async-await và Combine. Ngoại lệ là các dự án đơn giản với một nguồn dữ liệu duy nhất nơi Repository không có logic lưu trữ đệm.

Offline-first trong bối cảnh Repository là gì?

Offline-first là một chiến lược nơi ứng dụng hoạt động không có internet sử dụng dữ liệu cục bộ. Repository đóng vai trò chính: đầu tiên trả về dữ liệu từ DataSource cục bộ, sau đó đồng bộ hóa với máy chủ trong nền. Người dùng thấy dữ liệu ngay lập tức, và Repository cập nhật dữ liệu sau khi tải từ mạng. Room với Flow cung cấp cập nhật UI phản ứng khi dữ liệu thay đổi trong cơ sở dữ liệu cục bộ.

Tổng kết

  • Repository Pattern — một lớp trừu tượng giữa UI và các nguồn dữ liệu
  • DataSource — các lớp riêng biệt cho API, DB và bộ nhớ đệm
  • Giao thức — cần thiết cho kiểm thử và thay thế triển khai
  • Chiến lược lưu trữ đệm — Cache-First, Network-Only, Network-First-With-Cache-Backup
  • iOS — async-await hoặc Combine với các giao thức
  • Android — Kotlin Flow + Room + Retrofit, phương pháp được Google khuyến nghị
  • Kiểm thử — DataSources giả qua DI, xác minh chiến lược lưu trữ đệm

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm