En ofertas de empleo y blogs técnicos aparece con frecuencia la frase «basado en Clean Architecture». Sin embargo, al abrir el código, cada equipo lo implementa de forma distinta. Algunos usan UseCase y otros no, y las responsabilidades de Repository también varían.
La razón de la confusión es sencilla: Clean Architecture es un principio, no una estructura de carpetas ni una lista concreta de clases.
Hoy aclararemos en qué consiste ese principio y qué responsabilidad tiene cada uno de UseCase y Repository al implementarlos en iOS.
La clave es que «las dependencias solo apuntan hacia dentro»
Clean Architecture es un concepto organizado por Robert Martin (Uncle Bob) que contempla la aplicación como capas concéntricas.
- Capa más interna: dominio. Entidades y reglas de negocio. «¿Qué hace esta aplicación?»
- Capa intermedia: UseCase. Escenarios de comportamiento de la aplicación
- Capa externa: UI, bases de datos, red y frameworks
Solo hay una regla. Las dependencias siempre apuntan de fuera hacia dentro.
El código del dominio interno no debe conocer UIKit, URLSession ni SwiftData, que están fuera. En cambio, las capas externas sí pueden conocer las internas. Así, aunque cambiemos el framework de UI o la API del servidor, no tenemos que tocar las reglas centrales de la aplicación.
Como quizá hayas notado, esto amplía el DIP (Principio de Inversión de Dependencias) a toda la aplicación. Se aplica «depender de abstracciones, no de concreciones» a nivel de capas.
Repository: el límite que oculta «de dónde vienen los datos»
Repository es el límite entre el dominio y las fuentes de datos. El protocolo se coloca en el dominio y la implementación, fuera.
// Capa de dominio — URLSessionno SwiftDatasabe nada
protocol PostRepository {
func fetchPosts(userID: String) async throws -> [Post]
}
// Capa de datos — la implementación queda fuera
final class RemotePostRepository: PostRepository {
func fetchPosts(userID: String) async throws -> [Post] {
// URLSession llamada, DTO decodificación, Postconversión
}
}
La clave está en la ubicación del protocolo. Como el dominio posee la interfaz, solo sabe que «se pueden obtener publicaciones»; no sabe si proceden del servidor, de la caché o de una base de datos local. Si cambia el formato de respuesta del servidor, basta con modificar el DTO (Data Transfer Object, objeto de transferencia de datos que transporta la respuesta del servidor) y la implementación. En las pruebas, se puede conectar un Repository falso.
UseCase: el contenedor de una cosa que «hace la aplicación»
Un UseCase convierte en objeto un escenario de comportamiento de la aplicación, como «cargar el feed» o «publicar una entrada».
final class LoadFeedUseCase {
private let postRepository: PostRepository
private let blockRepository: BlockRepository
func execute(userID: String) async throws -> [Post] {
let posts = try await postRepository.fetchPosts(userID: userID)
let blocked = try await blockRepository.blockedUserIDs()
return posts
.filter { !blocked.contains($0.authorID) }
.sorted { $0.createdAt > $1.createdAt }
}
}
La regla de negocio «excluir del feed las publicaciones de usuarios bloqueados» pertenece a UseCase. ¿Qué ocurre si la colocamos en ViewModel? Las pantallas del feed, del perfil y de búsqueda implementarían cada una su propio filtro de bloqueados, y tarde o temprano una se desviaría. Al extraerla a UseCase, la regla vive en un solo lugar y puede probarse sin una pantalla.
Por eso MVVM (Model-View-ViewModel) y Clean Architecture no compiten, sino que son enfoques ortogonales. MVVM organiza la parte de View, mientras que Clean Architecture organiza las capas posteriores. En el artículo anterior dijimos que para evitar un Massive ViewModel hay que «bajar la lógica»; ese lugar son UseCase y Repository.
¿Hasta dónde deberíamos adoptarla?
La trampa de Clean Architecture es adoptarla en exceso. Si equipas una aplicación de dos pantallas con UseCase, Repository, DTO y Mapper, pasar un valor atraviesa cinco archivos. El problema del código repetitivo de VIPER (View·Interactor·Presenter·Entity·Router) se reproduce tal cual.
Un criterio práctico es el siguiente.
- **Repository casi siempre merece la pena.**Separar el código de red y DB de las pantallas facilita las pruebas y los cambios.
- **Añade UseCase cuando aparezcan reglas de negocio compartidas por varias pantallas.**Si la mayoría de UseCase solo reenvía llamadas a Repository, todavía es pronto.
- **Separa los modelos por capa (DTO/dominio/modelo de pantalla) cuando el proyecto crezca.**Separarlo todo desde el principio solo acumula código de Mapper.
Resumen
- Clean Architecture no es una estructura de carpetas, sino el principio de que «las dependencias solo apuntan hacia dentro (al dominio)». Es el DIP ampliado a la escala de la aplicación.
- Repository es el límite que oculta el origen de los datos, y la clave es que el dominio posee el protocolo.
- UseCase es el lugar de las reglas de negocio compartidas por varias pantallas. Es ortogonal a MVVM, por lo que se usan juntos.
- El objetivo no es incluirlo todo. Empieza por Repository y añade UseCase cuando se acumulen reglas.
En el próximo artículo cambiaremos de dirección para tratar TCA (The Composable Architecture), que convierte la gestión del estado en una arquitectura.

![Imagen de portada de [Arquitectura iOS #6] Fundamentos de Clean Architecture](/assets/images/posts/4a332cf7-d161-401b-ab7c-f15324e48609/1.jpg)