Repository Pattern — un patrón que añade una capa de abstracción entre la lógica de negocio y las fuentes de datos. En lugar de llamar directamente a la API, base de datos o caché, el Repository proporciona una interfaz unificada para obtener y almacenar datos. Esto simplifica las pruebas y el cambio entre fuentes. Más información en la documentación de Android Data Layer.
Puntos clave
Repository Pattern es un patrón estructural que aísla la lógica de negocio del acceso directo a las fuentes de datos. En lugar de que una Activity, UIViewController o ViewModel llame directamente a Retrofit, URLSession, Room o CoreData, se comunican con el Repository. El Repository decide de dónde obtener los datos — de la red, la base de datos o la caché — y devuelve el resultado en un formato unificado. Esto implementa el principio de responsabilidad única: la UI no sabe cómo ni de dónde se obtuvieron los datos.
Los componentes del Repository incluyen una interfaz (protocolo), una implementación y uno o más DataSources. Un DataSource es una clase que trabaja con una sola fuente: RemoteDataSource llama a la API mediante un cliente HTTP, LocalDataSource lee y escribe en la base de datos. El Repository recibe los DataSources a través del constructor (Inyección de Dependencias) y decide qué fuente utilizar. Por ejemplo, al solicitar una lista de usuarios, el Repository primero verifica la caché, luego la base de datos y luego la red.
Ventajas del Repository Pattern: el aislamiento de los cambios en las fuentes de datos (cambios de API, migraciones de BD) no afecta a la capa de UI; pruebas unitarias mediante la sustitución del Repository o DataSource; el almacenamiento en caché es transparente para la UI; cambio entre modos online y offline sin modificar la lógica de la pantalla. La comunidad de Android recomienda Repository como capa obligatoria en Clean Architecture.
La implementación en iOS del Repository se basa en protocolos de Swift. El protocolo Repository declara métodos para obtener y almacenar datos. La implementación real se inyecta a través del inicializador — esto permite reemplazar la implementación en pruebas y previsualizaciones de SwiftUI. Los DataSources también se declaran como protocolos: Protocol RemoteDataSource, Protocol LocalDataSource. La ViewModel o Interactor no conoce una implementación específica — solo el protocolo 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
}
}
Inyección de Dependencias en iOS para Repository se configura típicamente a través de una fábrica o contenedor DI (Swinject, Factory). En las pruebas, el protocolo UserRepository se reemplaza con una implementación mock que devuelve datos predefinidos. Async-await hace que el código sea síncrono y legible sin closures ni delegados. Para la reactividad con Combine, los métodos del Repository devuelven AnyPublisher en lugar de async throws.
La implementación en Android del Repository utiliza ampliamente Kotlin Coroutines y Flow para operaciones asíncronas. Google recomienda Repository en la guía oficial de arquitectura de Android (Android Architecture Components). El Repository acepta RemoteDataSource (Retrofit) y LocalDataSource (Room) a través del constructor, y la ViewModel se suscribe a un Flow del Repository. El Repository gestiona la estrategia de datos: caché primero, red primero, o siempre red con escritura en caché.
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 en el ejemplo anterior es estándar para Android: una clase sellada Result informa a la ViewModel sobre el estado de carga (Loading, Success, Error). La ViewModel se suscribe mediante collect y actualiza StateFlow o LiveData. Repository con Flow notifica automáticamente a la UI sobre cambios en la base de datos — esta es una diferencia clave con las solicitudes únicas donde la UI no sabe de los cambios sin actualización manual.
DataSource — clases responsables de trabajar con una fuente de datos específica. RemoteDataSource utiliza un cliente HTTP (URLSession, Retrofit, Ktor) para obtener datos de la API. LocalDataSource trabaja con almacenamiento local (CoreData, Realm, Room, UserDefaults, DataStore). Cada DataSource tiene una responsabilidad limitada: RemoteDataSource solo conoce el formato de la solicitud API, LocalDataSource — el esquema de la base de datos. El Repository los combina, implementando una estrategia de almacenamiento en caché.
| DataSource | Plataforma iOS | Plataforma Android | Fuente |
|---|---|---|---|
| Remote | URLSession + Codable | Retrofit + Moshi/Gson | API REST / GraphQL |
| Local (BD) | CoreData, SwiftData | Room, SQLDelight | SQLite en dispositivo |
| Local (caché) | NSCache, UserDefaults | DataStore, EncryptedSP | En memoria / disco |
| Preferencias | UserDefaults, Keychain | SharedPreferences, EncryptedSP | Ajustes, tokens |
Estrategias de almacenamiento en caché en Repository: Cache-First (caché primero, luego carga en segundo plano), Network-Only (solo red, para pantallas de pago), Network-First-With-Cache-Backup (red primero, fallback a caché en caso de error). La elección de la estrategia depende del escenario: una lista de países puede almacenarse en caché por mucho tiempo, los tipos de cambio — por 15 minutos, el saldo de la billetera — solo desde la red. El Repository implementa la estrategia y la cambia sin modificar la ViewModel o la UI.
Repository y Service son patrones diferentes con funciones superpuestas. Repository es responsable del acceso a datos y su almacenamiento en caché, devolviendo modelos de datos. Service (o Interactor, Use Case) contiene lógica de negocio: validación, transformación de datos, orquestación de llamadas a múltiples Repositories. Service puede combinar UserRepository, OrderRepository y NotificationRepository para procesar un pedido. Repository no contiene lógica de negocio — solo CRUD y almacenamiento en caché.
Cuándo elegir Repository — navegación de datos con múltiples fuentes (API + BD + caché), arquitectura offline-first, necesidad de almacenamiento en caché y cambio transparente de fuentes. Repository es obligatorio en Clean Architecture y recomendado por Google para aplicaciones Android. En la arquitectura VIPER en iOS, el rol de Repository lo desempeña la capa Interactor, que interactúa con Manager o Service para el acceso a datos.
Cuándo Service es suficiente — aplicaciones simples con una sola fuente de datos, pantallas de solo lectura sin escritura, proyectos sin modo offline. En tales casos, DataSource es utilizado directamente por la ViewModel o Presenter, y Repository se convierte en una capa innecesaria. Sin embargo, agregar Repository en una etapa temprana no requiere un esfuerzo significativo y simplifica la adición de almacenamiento en caché y pruebas en el futuro.
Preguntas frecuentes
DataSource es una clase que trabaja con una sola fuente (API, BD, caché). Repository es una clase que gestiona múltiples DataSources y proporciona una interfaz unificada. El Repository decide qué DataSource utilizar y coordina el almacenamiento en caché. DataSource no conoce la existencia de otras fuentes; Repository no conoce los detalles de implementación de cada fuente.
Sí, Repository es útil en SwiftUI para separar los datos de la View. La ViewModel se suscribe a un Publisher del Repository, y el Repository gestiona el almacenamiento en caché y la sincronización. En aplicaciones simples, se puede usar URLSession directamente en la ViewModel, pero para la testabilidad y escalabilidad, Repository es preferible. Apple no impone el patrón, pero es compatible con SwiftData y Network.framework.
Los DataSources se reemplazan con objetos mock mediante Inyección de Dependencias. La prueba crea un RemoteDataSource mock (devuelve JSON predefinido) y un LocalDataSource mock (verifica que los datos se guarden). El Repository se prueba de forma aislada: se verifica la estrategia de almacenamiento en caché, el manejo de errores y el orden correcto de las llamadas. Para pruebas de integración se utiliza TestDispatcher (Kotlin) o MainActor.run (Swift).
Es posible pero no se recomienda. Sin un protocolo, es imposible reemplazar la implementación en pruebas y previsualizaciones. En Kotlin, la interfaz Repository permite reemplazar la implementación mediante DI (Dagger, Hilt, Koin). En Swift, el protocolo Repository es obligatorio para probar código async-await y Combine. La excepción son proyectos simples con una sola fuente de datos donde Repository no tiene lógica de almacenamiento en caché.
Offline-first es una estrategia donde la aplicación funciona sin internet utilizando datos locales. Repository juega un papel clave: primero devuelve datos del DataSource local y luego se sincroniza con el servidor en segundo plano. El usuario ve los datos al instante, y Repository los actualiza después de cargarlos desde la red. Room con Flow proporciona actualizaciones reactivas de la UI cuando los datos cambian en la base de datos local.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también