Clean architecture es un tema que nunca pasa de moda en el mundo Android, y a partir de los comentarios y preguntas que recibo, me da la sensación de que todavía no está muy claro.
Sé que hay decenas (o probablemente cientos) de artículos relacionados con clean architecture pero aquí he querido dar un enfoque más pragmático / simplista que puede ayudar en tu primera incursión a la clean architecture. Es por eso que voy omitir conceptos que pueden parecer ineludibles para los puristas de la arquitectura.
Mi principal objetivo aquí es que entiendas lo que considero el punto principal (y más complicado) en clean architecture: la inversión de dependencias. Una vez que comprendas esó, puedes ir a otros artículos para rellenar los pequeños huecos que puedan haber quedado fuera.
Clean architecture: ¿por qué debería importarme?
Incluso si decides no utilizar arquitecturas en tus aplicaciones, creo que aprenderlas es realmente interesante, ya que te ayudará a entender conceptos muy importantes de la programación orientada a objetos.
Las arquitecturas permmiten desacoplar diferentes unidades de tu código de manera organizada. De esta forma el código se hace más fácil de entender, modificar y testear.
Pero las arquitecturas complejas, como la clean architecture pura, también pueden tener el efecto contrario: desacoplar el código también significa crear un montón de fronteras, modelos, transformaciones de datos… que pueden terminar aumentando la curva de aprendizaje de tu código hasta un punto en el que no merezca la pena.
Así que, como se debe hacer con todo lo que se aprende, pruébalo en el mundo real y decide qué nivel de complejidad estás dispuesto a introducir. Dependerá del equipo, el tamaño de la aplicación, el tipo de problemas que resuelve…
¡Así que vamos a empezar! En primer lugar, vamos a definir las capas que va a usar nuestra aplicación.
Las capas de clean architecture
Este punto se puede enfocar de maneras muy diferentes. Sin embargo, por simplicidad, me voy a limitar a 5 capas (que ya es lo suficientemente complejo de todas formas 😂):
1. Presentación
Es la capa que interactúa con la interfaz de usuario. Es probable que veas esta capa dividida en dos en otros ejemplos, ya que técnicamente se podría extraer todo menos las clases de la arquitectura a otra capa. Pero en la práctica, casi nunca se le saca partido, y complica las cosas.
Esta capa de presentación por lo general consiste en la interfaz de usuario de Android (activities, fragments, views) y presenters o view models, según el patrón de presentación que decidas utilizar. Si usa MVP, tengo un artículo donde explico en profundidad (y me gustaría escribir uno sobre MVVM en breve).
2. Casos de uso
A veces también se les llama interactors. Se trata principalmente de las acciones que el usuario puede desencadenar. Estos pueden ser acciones activas (el usuario hace clic en un botón) o acciones implícitas (la App navega a una pantalla).
Si quieres ser extra-pragmático, puedes incluso quitarte esta capa. A mí gusta porque por lo general es el punto en el que me cambio de hilo. A partir de este punto, puedo ejecutar todo lo demás en un un hilo secundario y olvidarme de tener cuidado con el hilo de UI. De esta forma, no necesito preguntarme más si algo se está ejecutando en el hilo de interfaz de usuario o un hilo de background.
3. Dominio
También conocida como la lógica de negocio. Estas son las reglas de tu negocio.
Contiene todos los modelos de negocio. Por ejemplo, en una App de películas, podría ser la clase Movie
, la clase de Subtitles
, etc.
Idealmente, debería ser la capa más grande, aunque es cierto que Apps Android por lo general lo único que hacen es dibujar una API en la pantalla de un teléfono, por lo que la mayor parte de la lógica va a consistir simplemente en la solicitud y la persistencia de datos.
4. Datos
En esta capa se encuentra una definición abstracta de las diferentes fuentes de datos, y la forma en que se debe utilizar. Aquí se suele usar un patrón repositorio que, para una determinada solicitud, es capaz de decidir dónde encontrar la información.
Una App típica podría guardar sus datos localmente y recuperarlos desde la red. Así que esta capa puede comprobar si los datos están en una base de datos local. Si están ahí y no están caducadon, devolverlos como resultado, y de lo contrario pedirlos a la API y guardarlos localmente.
Pero los datos no tienen que provenir solamente de una petición a un API. Es posible, por ejemplo, utilizar los datos de los sensores del dispositivo, o de un BroadcastReceiver (¡aunque la capa de datos nunca debe saber acerca de este concepto! Lo veremos más adelante)
5. Framework
Puedes encontrar esta capa con muchos nombres distintos. Básicamente encapsula la interacción con el framework, por lo que el resto del código puede ser agnóstico y reutilizable en caso de que quieras desarrollar la misma aplicación para otra plataforma (una opción real hoy en día con los proyectos multi-plataforma en Kotlin). Con “framework” no sólo me refiero al framework de Android, sino a cualquier biblioteca externa que queremos ser capaces de reemplazar fácilmente en el futuro.
Por ejemplo, si la capa de datos debe persistir algo, aquí se podría utilizar Room para hacerlo. O si tienes que hacer una petición, se usaría Retrofit. O se puede acceder a los sensores para solicitar alguna información. ¡Lo que sea que necesites!
Esta capa debe ser tan simple como sea posible, ya que toda la lógica debería ser abstraída en la capa de datos.
¡Recuerda! Estas son las capas sugeridas, pero algunas de ellas se pueden combinar. Incluso se puede simplemente tener tres capas: presentación – dominio – framework. Esto probablemente no puede llamarse estrictamente clean architecture, pero sinceramente me dan un poco igual los nombres. Dejaré 5 capas, ya que ayuda a explicar el punto siguiente, que creo que es el importante.
Interacción entre capas
Esta es la parte más difícil de explicar y entender. Voy a tratar de ser lo más claro posible, porque creo que este es también el punto más importante si se quiere entender la clean architecture. Pero no dudes en escribirme si no entiendes algo, y actualizaré el artículo para aclararlo.
Cuando se piensa en una forma lógica de interacción, se diría que la presentación utiliza la capa de casos de uso, que a su vez va a utilizar el dominio para acceder a la capa de datos, la que finalmente va a utilizar el framework para obtener acceso a los datos solicitados. A continuación, estos datos van de vuelta por la estructura de capas hasta llegar a la capa de presentación, que actualiza la interfaz de usuario. Esto sería un gráfico sencillo de lo que está sucediendo:

Como puedes ver, los dos límites del flujo dependen del framework, por lo que requieren el uso de la dependencia de Android, mientras que el resto de las capas sólo requieren Kotlin. Esto es realmente interesante si desea dividir cada capa en un sub-módulo por separado. Si fueses a reutilizar este mismo código para (digamos) una aplicación web, simplemente necesitarías reimplementar las capas de presentación y framework.
Pero no mezcles el flujo de la aplicación con la dirección de las dependencias entre las capas. Si has leído acerca de la clean architecture antes, es probable que vieras este gráfico:

Que es un poco diferente de la imagen anterior. Los nombres también son diferentes, pero vemos esto en un minuto. Básicamente, la clean architecture dice que tenemos capas exteriores e interiores, y que las capas internas no deben saber nada sobre las externas. Esto significa que una clase externa puede tener una dependencia explícita de una clase interna, pero no al revés.
Vamos a recrear el gráfico anterior con nuestras propias capas:

Así que desde la interfaz de usuario hacia la de dominio, todo es bastante simple, ¿verdad? La capa de presentación tiene una dependencia de casos de uso, y es capaz de llamar para iniciar el flujo. A continuación, el caso de uso tiene una dependencia al dominio.
Sin embargo, los problemas aparecen cuando vamos desde el interior hacia el exterior. Por ejemplo, cuando la capa de datos necesita algo del framework. Como se trata de una capa interna, la capa de datos no sabe nada acerca de las capas externas, por lo que ¿cómo puede comunicarse con ellos?
Presta atención, que aquí viene lo importante.
Principio de Inversión de Dependencia
Si has oído hablar de los principios SOLID, puede que hayas leído acerca del principio de inversión de dependencias. Pero, como pasa con muchos de estos conceptos, es posible que no que no te quedara claro cómo aplicarlo a un ejemplo real. La inversión de la dependencia es la “D” de los principios SOLID, y esto es lo que dice:
A. Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.
B. Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.
Sinceramente esto a mí no me dice mucho mucho en mi trabajo del día a día. Sin embargo, con este ejemplo, te va a ser más fácil entenderlo:
Un ejemplo de inversión de dependencia
Digamos que tenemos un DataRepository
en la capa de datos que requiere una RoomDatabase
para recuperar unos elementos guardados en un base de datos. El primer enfoque sería tener algo como esto.
Este es nuestro RoomDatabase
:
class RoomDatabase { fun requestItems(): List<Item> { ... } }
Y el DataRepository
utilizaría una instancia de la misma:
class DataRepository { private val roomDatabase = RoomDatabase() fun requestItems(): List<Item> { val items = roomDatabase.requestItems() ... return result } }
¡Pero esto no es posible! La capa de datos no sabe nada de las clases en Framework porque es una capa más interna.
Así que el primer paso es hacer una inversión de control (no mezclar con la inversión de dependencias, no son lo mismo), lo que significa que en lugar de crear instancias de la clase nosotros mismos, dejamos nos vengan dadas del exterior (a través del constructor):
class DataRepository(private val roomDatabase: RoomDatabase) { fun requestItems(): List<Item> { val items = roomDatabase.requestItems() ... return result } }
Fácil ¿verdad? Pero aquí es donde tenemos que hacer la inversión de dependencias. En vez de depender de la implementación específica, vamos a depender de una abstracción (una interfaz). Por lo que el módulo de dominio tendrá la siguiente interfaz:
interface DataPersistence { fun requestItems(): List<Item> }
Ahora el DataRepository
puede utilizar la interfaz (que se encuentra en su misma capa):
class DataRepository(private val dataPersistence: DataPersistence) { fun requestItems(): List<Item> { val items = dataPersistence.requestItems() ... return result } }
Y ya que la capa de datos puede utilizar la capa de dominio, puede implementar esa interfaz:
class RoomDatabase : DataPersistence { override fun requestItems(): List<Item> { ... } }
El único punto que faltaría es buscar una forma de proporcionar la dependencia al DataRepository
. Eso se hace mediante inyección de dependencias. Las capas externas se harán cargo de ella.
No voy a profundizar en la inyección de dependencia en este artículo, ya que no quiero añadir muchos conceptos complejos. tengo un unos cuantos artículos que hablan sobre ello y sobre Dagger, por si quieres ampliar sobre el tema.
Implementando un ejemplo
Lo primero de todo es que puedes encontrar el ejemplo completo en este repositorio. Voy a omitir algunos detalles, así que puedes ir allí a echarle un ojo, hacer fork y jugar un poco con el ejemplo.
Todo esto está muy bien, pero hay que ponerlo en práctica si queremos entenderlo por completo.
Para ello, vamos a crear una App que permitirá solicitar la ubicación actual gracias a un botón y mantener un registro de las ubicaciones previamente solicitadas, mostrándolas en una lista.

Creación de un proyecto de ejemplo
El proyecto consistirá en un conjunto de 5 módulos.
Por simplicidad, sólo voy a crear 4:
app: Será el único proyecto que utiliza el framework de Android, que incluirá las capas de presentación y de Framework.
- usecases: Será un módulo Kotlin (no necesita el framework de Android).
- dominio: Otro módulo Kotlin.
- datos: Un módulo Kotlin también.
Se podría hacer perfectamente todo en un único módulo, y utilizar paquetes en su lugar. Pero es más fácil de no violar el flujo de dependencias si se hace así. Así que recomiendo te lo recomiendo si te estás iniciando.
Crea un nuevo proyecto, que creará automáticamente el módulo de aplicación, y luego crea los módulos adicionales de Java:

No hay una opción “Kotlin library”, por lo que tendrás que añadir el soporte para Kotlin después.
La capa de dominio
Necesitamos una clase que representa la ubicación. En Android, ya tenemos una clase Location. Pero recordemos que queremos dejar los detalles de implementación en las capas exteriores.
Imagina que quieres utilizar este código para una aplicación web escrita en KotlinJS. Aquí no tendrías acceso a las clases de Android.
Así que esta es la clase:
data class Location(val latitude: Double, val longitude: Double, val date: Date)
Si has leído sobre esto antes, una clean architecture pura tendría una representación del modelo por capa, que en nuestro caso implicaría tener una clase Location en cada capa. A continuación, se usarían transformaciones de datos para convertirlos al pasar de una capa a otra. Eso hace que las capas estén menos acopladas, pero también todo lo más complejo. En este ejemplo, sólo voy a hacerlo cuando sea estrictamente necesario.
Esto es todo lo que necesitas en esta capa para este ejemplo sencillo.
La capa de datos
La capa de datos normalmente se modela usando repositorios que acceden a los datos que neceistamos. Podemos tener algo como esto:
class LocationsRepository { fun getSavedLocations(): List<Location> { ... } fun requestNewLocation(): List<Location> { ... } }
Tiene una función para obtener las ubicaciones pedidas anteriormente, y otra función para solicitar una nueva.
Como se puede ver, esta capa está utilizando la capa de dominio. Una capa exterior puede utilizar las capas internas (pero no a la inversa). Para ello, es necesario agregar una nueva dependencia al build.gradle
` del módulo:
dependencies { implementation project(':domain') ... }
El repositorio se va a utilizar un par de orígenes (o sources):
class LocationsRepository( private val locationPersistenceSource: LocationPersistenceSource, private val deviceLocationSource: DeviceLocationSource )
Uno de ellos tiene acceso a las ubicaciones almacenadas, y el otro a la ubicación actual del dispositivo.
Y aquí es donde sucede la magia inversión de dependencias. Estas dos fuentes son interfaces:
interface LocationPersistenceSource { fun getPersistedLocations(): List<Location> fun saveNewLocation(location: Location) } interface DeviceLocationSource { fun getDeviceLocation(): Location }
Y la capa de datos no sabe (y no necesita saber) cuál es la implementación real de estas interfaces. Las ubicaciones almacenadas y del dispositivo deben ser gestionadas por el framework específico del dispositivo. Una vez más, volviendo al ejemplo de KotlinJS, una aplicación web implementaría esto de forma muy diferente a la de una App para Android.
Ahora, el LocationsRepository
puede utilizar estas fuentes sin necesidad de conocer la implementación final:
fun getSavedLocations(): List<Location> = locationPersistenceSource.getPersistedLocations() fun requestNewLocation(): List<Location> { val newLocation = deviceLocationSource.getDeviceLocation() locationPersistenceSource.saveNewLocation(newLocation) return getSavedLocations() }
La capa de Casos de Uso
Esta es por lo general una capa muy simple, que simplemente convierte las acciones del usuario en las interacciones con el resto de capas internas. En nuestro caso, vamos a tener un par de casos de uso:
GetLocations
: Devuelve las ubicaciones que ya han sido registradas por la aplicación.RequestNewLocation
: Le dirá alLocationsRepository
que pida la ubicación actual.
Estos casos de uso tendrán una dependencia al LocationRepository
:
class GetLocations(private val locationsRepository: LocationsRepository) { operator fun invoke(): List<Location> = locationsRepository.getSavedLocations() } class RequestNewLocation(private val locationsRepository: LocationsRepository) { operator fun invoke(): List<Location> = locationsRepository.requestNewLocation() }
La capa del Framework
Esta va a ser parte del módulo de app
, e implementará principalmente las dependencias que se ofrecen al resto de capas. En nuestro caso particular, será LocationPersistenceSource
y DeviceLocationSource
.
El primero podría ser implementado con Room, por ejemplo, y el segundo con el LocationManager
. Pero con la intención de hacer esta explicación más simple, voy a utilizar implementaciones fake. Podría implementar la solución real en algún momento, pero esto sólo añadiría complejidad a la explicación, por lo que prefiero que nos olvidemos de ello por ahora.
Para la persistencia, voy a usar una implementación en memoria sencilla:
class InMemoryLocationPersistenceSource : LocationPersistenceSource { private var locations: List<Location> = emptyList() override fun getPersistedLocations(): List<Location> = locations override fun saveNewLocation(location: Location) { locations += location } }
Y un generador aleatorio para el otro:
class FakeLocationSource : DeviceLocationSource { private val random = Random(System.currentTimeMillis()) override fun getDeviceLocation(): Location = Location(random.nextDouble() * 180 - 90, random.nextDouble() * 360 - 180, Date()) }
Piensa en esto en un proyecto real. Gracias a las interfaces, durante la implementación de una nueva funcionalidad, se pueden proporcionar dependencias fake mientras se trabaja en el resto del flujo, y olvidarse de los detalles de implementación hasta el final.
Esto también demuestra que estos detalles de implementación son fácilmente intercambiables. Así podrías hacer que tu App trabaje inicialmente con persistencia en memoria, y luego más adelante usar una base de datos. Se pueden implementar como queramos, y luego reemplazarlos.
O imagina que una nueva biblioteca aparece (como Room 😂) y quieres probarlo y considerar una posible migración. Sólo tienes que implementar la interfaz utilizando la nueva librería, sustituir la dependencia, ¡y ya lo tienes funcionando!
Y, por supuesto, esto también ayuda en los tests, en el que podemos sustituir estos componentes por fakes o mocks.
La capa de presentación
Y ahora podemos implementar la interfaz de usuario. Para este ejemplo, voy a usar MVP, porque este artículo se originó por las preguntas en el artículo original sobre MVP y porque creo que es más fácil de entender que el uso de MVVM con architecture components. Sin embargo, ambos enfoques son muy similares.
En primer lugar, tenemos que escribir el Presenter, el cual recibirá una vista como dependencia (la interfaz del Presenter para interactuar con su vista) y los dos casos de uso:
class MainPresenter( private var view: View?, private val getLocations: GetLocations, private val requestNewLocation: RequestNewLocation ) { interface View { fun renderLocations(locations: List<Location>) } fun onCreate() = launch(UI) { val locations = bg { getLocations() }.await() view?.renderLocations(locations) } fun newLocationClicked() = launch(UI) { val locations = bg { requestNewLocation() } view?.renderLocations(locations.await()) } fun onDestroy() { view = null } }
Todo muy sencillo, dejando a un lado la forma de realizar tareas en segundo plano. He usado corrutinas (de hecho, la librería Anko, para utilizar bg
). No estoy seguro de si es la mejor decisión, porque quería mantener este ejemplo tan fácil como sea posible. Así que si no se entiendo bien, indícamelo en los comentarios. Puedes saber más sobre las corrutinas en este artículo que escribí hace algún tiempo.
Por último, el MainActivity
. Con el fin de evitar el uso de un inyector de dependencias, he declarado aquí las dependencias:
private val presenter: MainPresenter init { // This would be done by a dependency injector in a complex App // val persistence = InMemoryLocationPersistenceSource() val deviceLocation = FakeLocationSource() val locationsRepository = LocationsRepository(persistence, deviceLocation) presenter = MainPresenter( this, GetLocations(locationsRepository), RequestNewLocation(locationsRepository) ) }
No recomiendo usar esto para una App grande, ya que se podrían reemplazar las dependencias en los tests de UI, por ejemplo, pero es suficiente para este ejemplo.
Y el resto del código no necesita mucha explicación. Tenemos un RecyclerView
y un botón. Cuando se hace clic en el botón, se llama al Presenter para que se solicite una nueva ubicación:
newLocationBtn.setOnClickListener { presenter.newLocationClicked() }
Y cuando el Presenter termina, se llama al método de la View
. Esta interfaz la implementa la Activity de esta manera:
override fun renderLocations(locations: List<Location>) { locationsAdapter.items = locations }
Conclusión
¡Esto es todo! Los fundamentos de la clean architecture son de hecho bastante simples.
Sólo es necesario entender cómo funciona la inversión de dependencias, y luego enlazar las capas correctamente. Sólo recuerda no agregar dependencias de los módulos externos a los internos, y tendrás mucha ayuda del IDE para hacer las cosas bien.
Sé que esto es una mucha información de golpe. Mi objetivo es que esto sirve como un punto de entrada para las personas que nunca vieron la clean architecture antes. Así que todavía tienes dudas, házmelo saber en los comentarios y reescribiré este artículo tantas veces como sea necesario.
Y también recuerda que con el fin de hacer este simple artículo, he omitido algunas complejidades que encontrarías en una clean architecture habitual. Una vez que tengas esto claro, te sugiero que leas otros ejemplos más completos. Siempre me gusta recomendar éste de Fernando Cejas.
El enlace al repositorio de Github está aquí. Puedes ir allí para echar un ojo a los pequeños detalles, y si te gusta, por favor, házmelo saber con una estrella 🙂
Es un excelente artículo, a pesar de qué hay muchos artículos en internet sobre MVP o MVVM, nunca está demás reforzarlo, estos temas son bien confusos sobre todos a la hora de diseñar bien los objetos.
Saludos
Muy buen articulo como siempre Antonio, un pequeño detalle que creo que lo mejoraría. Seria bueno que en la capa de use cases hicieras el cambio de hilo que mencionas que haces en el articulo. Para entender un poco mejor el por que se necesitaría esta capa, y para que el articulo sea totalmente coherente con el repositorio, creo que ayudaría mucho a los primerizos con este tema.
Gracias Jose! En realidad el cambio de hilo se hace al llamar a los use cases desde las corrutinas.
Igual no está bien explicado, voy a revisar el texto a ver si puedo mejorarlo.
Quiero hacerte una sugerencia para tu blog. Estaría genial si añadieras la fecha de publicación en la parte superior de los artículos. Sin ella uno puede tener la sensación de no saber si estás leyendo un artículo del 2008 o del 2018…
Aún no he leído el artículo, pero pienso hacerlo pues lo he ojeado y tiene buena pinta. Estoy especialmente interesado en aprender a usar interfaces para programar “contra una abstracción” y menos sobre implementaciones concretas. Y para aprender a usarlas en el intercambio de “mensajes” entre capa y capa.
Gracias por la sugerencia! Lo valoraré Espero que te sea de ayuda el artículo.
Hola Antonio! Excelente artículo. Solo para aclarar, la interacción entre capas o el flujo que sigue para obtener datos (por ejemplo):
Presentación -> Casos de Uso -> Dominio -> Data -> Framework;
no es lo mismo que el “flujo” que sigue el principio de inversión de dependencias
(Presentación -> Casos de Uso -> Dominio <- Data <- Framework),
cierto?
Gracias!!
Exacto! Justo eso es
Hola Antonio! Muy buen post.
He realizado los pasos para conseguir una base “clean” para mis apps. He implementado Room en la capa del Framework y ahora quiero añadir LiveData para la propagación de los cambios de la BBDD. Pero me encuentro con el problema de que en la capa data no puedo retornar LiveData<List> porque no están las dependencias de Android. ¿Cómo es posible “conectar” la capa Framework con la de UI mediante LiveData?
Es tal la magia, que se me escapa por algún lado 😉
Gracias!
Hola Jesús! Realmente no puedes. LiveData vendría a ser lo mismo que Rx. Si lo incluyes en tu arquitectura, toda la arquitectura se “contamina” de ello, así que no queda más remedio que añadirlo como una dependencia en todas las capas. La desventaja de LiveData es que (creo que) te obliga a que esa capa también tenga dependencias con el framework de Android.
Es lo que me imaginaba. En ese caso, creo que separaré las capas por paquetes y no por módulos.
Muchas gracias Antonio.
Un artículo excelente. Con el ejemplo se ve todo clarísimo.
Tengo una duda con respecto a la dependencias entre capas:
(Presentación -> Casos de Uso -> Dominio <- Data Data
¿Es así o me he liado?
Esto me descuadra el gráfico de los tres círculos que presentas.
A mi me da el siguiente esquema de dependencias:
(Presentación -> Casos de Uso -> Data Dominio)
Mil gracias
Gracias! las dependencias van de fuera hacia adentro, todo lo que está fuera puede ver lo de dentro pero no al revés. Así que efectivamente, data podría usar clases que hay en dominio, pero el dominio no puede usar las clases que haya en data.
Muchas gracias por la rápida respuesta.
Disculpas, pero me temo que el mensaje anterior se pegó mal. Trato de explicarme mejor.
Mi duda está en la relación entre los módulos Casos de uso y Data.
En el diagrama de los tres círculos, interpreto que no tienen dependencias entre sí, pero en el proyecto queda claro que Casos de Uso tiene una dependencia hacia Data.
Por lo tanto, ¿No habría que dibujar Data en un círculo interior a Casos de uso?
Sí, tienes toda la razón. Lo detecté hace un tiempo pero aún no he tenido tiempo de modificarlo. La capa de casos de uso va pegada a la de presentación, y la de data es más interior porque la de casos de uso puede usarla.
Excelente articulo.
Estoy iniciando en esto de la arquitectura limpia en android. Tengo una duda. En la capa de Casos de Uso podría ir logica de negocio?. Ejemplo. Validación si un usuario esta activo o no, o si un atributo de X objeto tiene una fecha determinada fijar un estado en el mismo.
De antemano gracias.
Hola Antonio,
Muchas gracias por tus explicaciones. Si a parte del repositorio tuviésemos una descarga de datos a través de una petición a una API ¿Tendríamos un caso de uso de obtener localización (para ir de la UI a la entidad) y otro que sería obtener localización (para ir de la API a la entidad)?
Hago una especie de flujo:
Estamos en la UI y pulsamos un botón para descargar las localizaciones -> vamos al caso de uso obtener localizaciones -> vamos a la entidad.
El anterior sería el flujo hacia la entidad y la entidad tiene que responder pero para eso tiene que hacer una petición al caso de uso, el caso de uso al repositorio y este a su vez al framework donde está la api para descargar los datos. ¿Sería este el flujo con inversión de dependencias?
entidad <- caso de uso <- repositorio caso uso petición localizacion para la ui -> entidad <- caso de uso petición localizacion repositorio para la entidad <- repositorio <- llamada API
Habría entonces 2 casos de uso, el de pedir la localización al pulsar el botón (con respuesta a la UI) y el de pedir al servidor la localización (para responder a la entidad)
¿Estoy en lo correcto?
Muchas gracias, un saludo!
Hola José Miguel. No sé si me queda claro tu ejemplo. La API también devolvería algo de localización, o necesita la localización para hacer una llamada? En cualquier caso, si todo lo desencadena una misma acción del usuario, solo habría un caso de uso. Un caso de uso puede usar tantos repositorios como necesite. Y una llamada a una API, si es para recuperar o modificar datos, también estará normalmente escondida como un data source dentro de un repositorio. El mismo ejemplo de la app, si la localización se pudiera obtener desde un servidor, podría tener una tercera fuente de datos que fuera la petición al servidor. No sé si esto repsonde a tu pregunta, si no dime y vuelvo a contestarte. Un saludo!
existiria la posibilidad de hacer el mismo post para java?¿
No sorry, a día de hoy el mundo Android está enfocado a Kotlin, y mi principal motivación es animar a los devs Android a que hagan esa transición. Las ideas son las mismas en cualquier caso, no debería costarte mucho abstraerte del lenguaje para comprenderlo. Ánimo con ello!