La modularidad es un principio mediante el cual una aplicación se ensambla a partir de módulos independientes, cada uno responsable de una única funcionalidad. Según Android Developers, la división en módulos acelera la compilación gracias a la compilación paralela y permite que los equipos trabajen en diferentes partes de la aplicación de forma independiente. La arquitectura modular se ha convertido en el estándar para grandes proyectos móviles con decenas de desarrolladores.
Puntos clave
La modularidad es una forma de organizar el código donde una aplicación consiste en módulos débilmente acoplados, cada uno proporcionando una funcionalidad estrictamente definida a través de una interfaz pública. A diferencia de la arquitectura monolítica donde todas las clases residen en un solo proyecto, el enfoque modular divide el código en unidades de compilación físicamente independientes.
El objetivo principal de la modularidad es la gestión de la complejidad. Un desarrollador puede centrarse en un módulo sin tener en mente toda la base de código. Cada módulo tiene su propia área de responsabilidad y puede desarrollarse, probarse e implementarse independientemente de los demás. Esto es especialmente valioso en proyectos con 10+ desarrolladores, donde el trabajo paralelo sobre un monolito provoca frecuentes conflictos de fusión.
Es importante distinguir la modularidad de la arquitectura en capas. Las capas (Presentación, Dominio, Datos) dividen el código por criterios técnicos, mientras que los módulos lo hacen por criterios funcionales. Un módulo de “Perfil de usuario” puede contener sus propias capas internas. En la práctica, el enfoque modular y la arquitectura en capas se combinan: cada módulo tiene su propia estructura de tres capas.
Los módulos de funcionalidad son el tipo de módulo más popular. Cada pantalla o grupo de pantallas relacionadas se separa en su propio módulo: Onboarding, Profile, Settings, Feed. Un módulo de funcionalidad contiene todo lo necesario para que la función funcione: interfaz de usuario, lógica de negocio, capa de datos. Los límites del módulo están protegidos: otras funciones no pueden acceder a sus clases internas.
Los módulos centrales contienen infraestructura común: redes, bases de datos, analítica, sistema de diseño. No dependen de los módulos de funcionalidad, pero los módulos de funcionalidad dependen de ellos. Esta separación garantiza que cambiar un SDK de analítica no afecte a la capa de red, y viceversa. Los módulos centrales se reutilizan entre funcionalidades sin duplicación de código.
Los módulos compartidos contienen código utilizado por múltiples funcionalidades: modelos de datos, utilidades, constantes, vistas personalizadas. El principal problema de los módulos compartidos es el riesgo de convertirse en un vertedero (“módulo misceláneo”) donde se acumula código heterogéneo con el tiempo. Regla: un módulo compartido debe tener un tema claro, por ejemplo “shared-ui” o “shared-models”.
En Android, los módulos compartidos a menudo se separan en bibliotecas con el prefijo lib: lib-network, lib-database, lib-ui-components. En iOS, la misma función la realizan los Swift Packages internos dentro de un Workspace. En la práctica, los equipos limitan el número de módulos compartidos a 3–5 para evitar crear una red de dependencias excesiva que complique la compilación.
Los módulos de prueba separados permiten ejecutar pruebas solo para el módulo modificado sin ejecutar todo el conjunto de pruebas. Esto reduce el tiempo del pipeline CI/CD de horas a minutos. El aislamiento a nivel de módulo garantiza SoC a nivel de compilación: un módulo de capa de red no puede importar accidentalmente bibliotecas de interfaz de usuario en sus pruebas.
Cada módulo debe tener una API pública claramente definida. En Android, esto se logra mediante modificadores de acceso y api vs implementation en Gradle. En iOS, mediante modificadores de acceso public/internal y dependencias gestionadas a través de Package.swift. Reducir la visibilidad al mínimo necesario es una práctica clave del diseño modular.
Gradle admite la arquitectura modular de forma nativa: cada módulo es una unidad de compilación separada con su propio archivo build.gradle. Los proyectos de Android utilizan una combinación de un módulo de aplicación (app) y varios módulos de biblioteca. Los módulos de biblioteca no se pueden ejecutar como una aplicación, pero se pueden publicar como AAR en un repositorio.
Una característica clave de Gradle es la compilación paralela de módulos independientes. Si los módulos A, B y C no dependen entre sí, Gradle los compila simultáneamente usando todos los núcleos de la CPU. En proyectos con 20+ módulos, esto reduce una compilación completa de 15 a 3–5 minutos. Las compilaciones incrementales de un módulo modificado toman segundos.
Gradle proporciona dos tipos de dependencias entre módulos: api (transitivas) e implementation (no transitivas). La diferencia es críticamente importante para la modularidad: implementation oculta las dependencias transitivas de los consumidores del módulo. Si el módulo :profile usa :networking a través de implementation, los consumidores de :profile no conocen :networking y no pueden acceder a él.
// settings.gradle — declaración de módulos
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — dependencias del módulo
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
El código muestra la estructura de un proyecto Android modular. Settings.gradle enumera todos los módulos, y el build.gradle de cada módulo de funcionalidad especifica solo los módulos centrales que necesita. El sistema de compilación resuelve automáticamente las dependencias transitivas y compila los módulos en el orden correcto.
Swift Package Manager (SPM) ha sido la herramienta de modularidad estándar en iOS desde 2019. SPM permite dividir una aplicación en Swift Packages, cada uno de los cuales puede ser una biblioteca o un ejecutable. Un Package define módulos (targets) y sus dependencias a través de Package.swift. SPM está integrado en Xcode y no requiere herramientas adicionales.
CocoaPods sigue siendo el gestor de dependencias principal para bibliotecas de terceros. Podfile y Podspec definen la estructura modular, y CocoaPods genera un workspace con proyectos pod separados. Para la modularidad de su propio proyecto, los equipos eligen cada vez más SPM porque está integrado en Xcode y no requiere instalación.
En la modularidad de iOS, el control de acceso juega un papel importante: public, package, internal, fileprivate y private. Un módulo publica solo aquellos tipos que deben ser accesibles para otros módulos. Los detalles internos de implementación se ocultan detrás de los modificadores internal y private. Esto evita dependencias ocultas entre módulos.
// Package.swift — estructura modular de un proyecto iOS
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swift declara dos productos de biblioteca: ProfileFeature y NetworkCore. ProfileFeature depende de NetworkCore pero no conoce la existencia de Alamofire: está oculto dentro de NetworkCore. Este aislamiento es una aplicación directa de SoC a nivel de módulo: los cambios en el cliente HTTP no requieren recompilación de ProfileFeature.
La principal ventaja de la modularidad es la velocidad de desarrollo. Los equipos trabajan en paralelo en diferentes módulos sin conflictos de código. El pipeline CI/CD compila solo los módulos modificados y ejecuta solo sus pruebas. El tiempo de retroalimentación disminuye y la frecuencia de lanzamientos aumenta. Spotify, Uber y Airbnb publicaron casos de estudio de migración a arquitectura modular con mejoras de métricas de 2 a 3 veces.
La segunda ventaja es el aislamiento de errores. Un error en el módulo Profile no afecta al módulo Payments si no hay dependencias directas entre ellos. Esto es especialmente importante en aplicaciones con funcionalidades de alto riesgo (pagos, datos médicos), donde un error en una pantalla no relacionada no debe bloquear el lanzamiento de funcionalidad crítica.
El principal desafío es la gestión de dependencias. Con un diseño deficiente, surge un grafo de módulos donde cambiar un módulo provoca la recompilación en cascada de docenas de otros. La solución es seguir la regla de aciclicidad: el grafo de dependencias de módulos debe ser un grafo acíclico dirigido (DAG). Herramientas como Gradle Module Graph Assert ayudan a detectar ciclos en tiempo de compilación.
El segundo desafío es el mayor tiempo de configuración inicial. Crear una arquitectura modular requiere más tiempo en la etapa de inicialización del proyecto. Los proyectos pequeños con 1–3 desarrolladores pueden no beneficiarse de la modularidad, dedicando tiempo a mantener los límites de los módulos sin una necesidad real de paralelización. La solución es comenzar con un monolito y extraer módulos a medida que el equipo crece.
El enfoque feature-first agrupa los módulos por funcionalidad: cada pantalla o grupo de pantallas se convierte en un módulo separado. El enfoque layer-first divide el código por criterios técnicos: módulos separados para interfaz de usuario, lógica de negocio y datos. En la práctica, la mayoría de los equipos eligen feature-first con módulos centrales: esto proporciona un mejor aislamiento y una navegación clara del proyecto.
La elección entre enfoques depende del tamaño del equipo y la previsibilidad de las funcionalidades. Si sabes exactamente qué pantallas tendrá el proyecto, feature-first permite que cada desarrollador sea responsable de su propio módulo. Si la funcionalidad cambia con frecuencia y se superpone entre pantallas, layer-first proporciona más flexibilidad para reutilizar código entre diferentes funcionalidades.
Preguntas frecuentes
El número óptimo depende del tamaño del proyecto y del equipo. Para un equipo de 5 personas, 6–10 módulos son suficientes. Para 20+ desarrolladores, 20–40 módulos. Regla: un módulo debe ser lo suficientemente pequeño para que un desarrollador lo entienda completamente, y lo suficientemente grande para no crear una red de dependencias excesiva.
La modularidad adecuada acelera la compilación mediante compilación paralela y almacenamiento en caché. Sin embargo, un número excesivo de módulos con dependencias estrechas ralentiza la compilación: Gradle y Xcode dedican tiempo a resolver el grafo. La clave para compilaciones rápidas es minimizar las dependencias transitivas y mantener la aciclicidad.
Sí, pero de forma iterativa. Comienza extrayendo los módulos centrales (red, base de datos), luego extrae las funcionalidades una por una. Usa feature flags para habilitar el nuevo código modular junto con el código monolítico antiguo. La migración completa de una aplicación grande toma de 3 a 12 meses.
Los módulos son unidades de compilación dentro de una sola aplicación. Los microservicios son procesos separados que se ejecutan en diferentes servidores. Los módulos dividen el código, los microservicios dividen el runtime. En el desarrollo móvil, a menudo se usa el término “microapps” como híbrido: módulos de funcionalidad que pueden ejecutarse como aplicaciones independientes.
Cada módulo tiene sus propias pruebas unitarias que se ejecutan de forma independiente. Las pruebas de integración verifican la interacción entre módulos. Las pruebas de UI cubren los módulos de funcionalidad con datos simulados. La arquitectura modular simplifica las pruebas: simular una dependencia de otro módulo es más fácil que simular parte de un monolito.
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