Noticia Introducción a Clean Architecture: Separando capas en tu aplicación Android

Introducción a Clean Architecture


Cuando nos metemos de lleno en el desarrollo de aplicaciones para Android, nos damos cuenta de que mantener la escalabilidad y la legibilidad no es moco de pavo. Si no tenemos un plan arquitectónico sólido, el proyecto puede volverse un auténtico caos a medida que crece, convirtiéndose en lo que comúnmente llamamos código espagueti, donde tocar una línea en un sitio rompe algo en un lugar totalmente inesperado.

Para evitar estos dolores de cabeza, entra en juego la denominada Clean Architecture, popularizada por Robert C. Martin, más conocido como Uncle Bob. Este enfoque no es simplemente una moda, sino una filosofía de diseño que busca que el software sea modular, fácil de testear y mantenible a largo plazo, basándose en la separación estricta de responsabilidades y el respeto a los principios SOLID.

Los pilares de la Arquitectura Limpia​


En esencia, Clean Architecture organiza el código en capas concéntricas. La idea fundamental es que las dependencias siempre apunten hacia el interior; es decir, las capas externas pueden conocer a las internas, pero nunca al revés. Esto nos permite cambiar una base de datos o una librería de UI sin tener que reescribir la lógica de negocio central.

Generalmente, nos encontramos con tres bloques principales. Primero tenemos la Capa de Presentación, que es la cara visible de la app y donde se gestionan los ViewModels y la interfaz de usuario. Luego está la Capa de Dominio, que es el corazón del sistema y contiene las reglas de negocio, los modelos de entidad y los casos de uso. Por último, la Capa de Datos se encarga de la implementación concreta de los repositorios, gestionando las peticiones a APIs REST o las consultas a bases de datos locales.

Estructura y Modularización en Kotlin​


Si queremos llevar esto a la práctica en un proyecto de Kotlin, no basta con crear carpetas. Para que la separación sea real, lo ideal es utilizar módulos de Gradle independientes. Esto evita que, por un descuido, importemos una clase de datos directamente en la vista, rompiendo así la regla de oro de la arquitectura.

Existen diversas formas de organizar estos módulos. Algunos optan por un enfoque simple de división por capas (un módulo para data, otro para domain y otro para presentation), pero esto puede quedarse corto en apps muy grandes. Una alternativa mucho más potente es la separación por características o features. En este modelo, cada funcionalidad de la app (por ejemplo, el perfil de usuario o el carrito de compras) tiene sus propias capas internas de dominio, datos y presentación.

Adoptar una estructura modularizada no solo pone orden, sino que dispara la productividad al conseguir tiempos de compilación más rápidos, ya que Gradle solo recompila los módulos que han sufrido cambios. Además, facilita enormemente el trabajo en equipo, permitiendo que distintos desarrolladores trabajen en features independientes sin pisarse los pies.

Anatomía detallada de las capas​


Para entrar en detalle, la capa de dominio debe ser absolutamente pura. Esto significa que no debe contener ninguna referencia a Android, ni a Room, ni a Retrofit. Aquí es donde residen los Interactors o Casos de Uso, que definen exactamente qué hace la aplicación. Por ejemplo, un caso de uso llamado «ObtenerDatosUsuario» se encargaría de coordinar la lógica necesaria para traer esa información.

La capa de datos, por su parte, es la que «ensucia“ las manos. Aquí implementamos las interfaces definidas en el dominio. Es muy común utilizar el patrón Repository para abstraer el origen de los datos. El repositorio puede decidir si sirve la información desde una caché local o si tiene que hacer una petición de red, manteniendo al resto de la aplicación totalmente ignorante sobre de dónde vienen los datos.

Finalmente, la capa de presentación se encarga de gestionar el estado de la UI. Ya sea usando MVVM con Jetpack Compose o el patrón MVP, su única misión es llamar a los casos de uso y mostrar el resultado al usuario de la forma más eficiente posible. Es vital que esta capa no interactúe directamente con la base de datos, pasando siempre por el dominio.

El secreto del éxito: La Inversión de Dependencias​


Introducción a Clean Architecture: Separando capas en tu aplicación Android


Muchos desarrolladores se confunden al intentar aplicar Clean Architecture porque no entienden la Inversión de Dependencias (DIP). No hay que confundirlo con la Inyección de Dependencias (DI), que es la herramienta para lograrlo. La inversión de dependencias consiste en que los módulos de alto nivel no dependan de los de bajo nivel, sino que ambos dependan de abstracciones.

Imaginemos que necesitamos guardar datos en una base de datos Room. Si la capa de datos depende directamente de la clase de Room, estamos acoplados. La solución es crear una interfaz de persistencia en el dominio. La capa de datos implementa esa interfaz y, gracias a la inyección de dependencias (usando herramientas como Koin o Hilt), el sistema le entrega al repositorio la implementación concreta en tiempo de ejecución. De este modo, si mañana decidimos cambiar Room por Realm, solo tenemos que cambiar la implementación sin tocar una sola línea de la lógica de negocio.

Consejos prácticos y buenas prácticas​


Para que el proyecto no se vuelva una pesadilla de archivos repetidos, es recomendable seguir ciertas pautas. En primer lugar, evita la tentación de simplificar eliminando la capa de casos de uso en proyectos medianos; aunque parezca código redundante, es ahí donde se centraliza la lógica y se facilita el cambio de hilos de ejecución (del hilo principal al background).

Otro punto clave es la gestión de los modelos. Aunque lo ideal sería tener un modelo de datos distinto para cada capa (modelos de API, modelos de dominio y modelos de UI) y usar mappers para convertirlos, en proyectos más pequeños se puede compartir la entidad de dominio para no complicar demasiado el desarrollo. No obstante, en aplicaciones empresariales, esta separación es la que permite que el código sea realmente resistente a los cambios externos.

Implementar este sistema requiere disciplina. Es fundamental separar siempre las interfaces de sus implementaciones, colocando las interfaces en el dominio y las implementaciones en la capa de datos o framework. Solo así conseguiremos un sistema donde las piezas sean como bloques de LEGO, fáciles de quitar y poner sin que todo el edificio se venga abajo.

Continúar leyendo...