iOS

Cómo modularizar una app iOS grande sin frenar al negocio

Dambert Muñoz

Staff iOS Architect & AI Architect

5 min de lectura
#iOS#arquitectura iOS#modularización#Swift
Compartir

Hay un momento en la vida de toda app iOS exitosa en que cambiar un texto obliga a recompilar media aplicación, los merges se vuelven una negociación y nadie se atreve a tocar ciertos archivos. El código no está necesariamente mal escrito: creció más rápido que su estructura.

La reacción típica es proponer una reescritura o una gran migración a módulos que congela el roadmap durante meses. Casi nunca se aprueba, y cuando se aprueba, rara vez termina. Así abordo la modularización: como una decisión de arquitectura y también de organización, que avanza sin detener la entrega de features.

1. Mide el dolor antes de mover un archivo

Modularizar no es un objetivo; es una forma de reducir costes concretos. Antes de proponer nada, consigue números que el equipo y el negocio entiendan:

  • Tiempo de compilación incremental: cuánto tarda en compilar un cambio pequeño en una pantalla.
  • Tiempo hasta un PR en verde: compilación, tests y revisión en CI.
  • Archivos que cambian juntos: el historial de git muestra qué partes están acopladas de verdad, más allá del diagrama.
  • Ciclos de dependencias: tipos que se usan mutuamente entre zonas que deberían ser independientes.

Con esa línea base puedes decir algo mucho más útil que «el código está acoplado»: puedes decir qué cuesta, a quién le cuesta y cómo sabrás si mejoró.

2. Corta por capacidades de negocio, no por capas técnicas

El primer impulso suele ser crear módulos llamados Networking, Models o UIComponents. Ayudan poco: casi cualquier feature necesita tocarlos todos a la vez, así que el acoplamiento sigue igual, solo que repartido en más carpetas.

Los cortes que reducen la fricción siguen capacidades de negocio: Pagos, Checkout, Perfil, Notificaciones. Cada módulo encapsula sus pantallas, su lógica y su acceso a datos, y un equipo puede trabajar en él sin pisar al resto. Las piezas técnicas compartidas existen, pero deben ser pocas, estables y aburridas: diseño, red, analítica.

3. Separa la interfaz de la implementación

Es el patrón con más impacto en apps grandes. Cada capacidad expone dos módulos: uno de interfaz, con protocolos y modelos públicos, y otro de implementación, con todo lo demás. Una feature que necesita cobrar depende de PaymentsInterface, nunca de Payments.

  • Cambiar la implementación no obliga a recompilar a quienes la consumen.
  • Las implementaciones se compilan en paralelo, porque nadie depende de ellas salvo la app.
  • Cada feature puede tener su propia app de demostración y sus previews, que arrancan en segundos.
  • Las pruebas usan implementaciones falsas sin trucos de configuración.

La app principal es la única que conoce todas las implementaciones y las conecta al arrancar; es lo que se llama composition root. Ahí vive la inyección de dependencias, y el resto del código pide lo que necesita a través de interfaces.

4. Estrangula, no reescribas

La migración que funciona se parece al patrón strangler fig: el código nuevo nace en módulos y el existente se extrae por partes, una capacidad cada vez, mientras la app sigue publicándose con normalidad.

  1. Elige como primer candidato algo con pocas dependencias hacia el resto de la app y un equipo con ganas de hacerlo.
  2. Crea primero el módulo de interfaz y haz que el código existente lo use, aunque la implementación siga en el target principal.
  3. Mueve la implementación después, cuando nadie dependa ya de sus detalles.
  4. Repite. Cada extracción debe caber en un sprint y llegar a producción.

La primera extracción será lenta porque destapa todos los atajos del código. Las siguientes van más rápido porque el equipo ya conoce el camino. Por eso conviene que la primera sea una victoria visible y no la parte más enredada de la app.

5. Pon trinquetes, no reglas en una wiki

Las reglas de arquitectura que viven en un documento se rompen en el siguiente sprint con prisa. Las que viven en CI se cumplen solas. Un trinquete es una comprobación automática que deja avanzar pero no retroceder:

  • Una feature no puede importar la implementación de otra feature, solo su interfaz.
  • Los módulos compartidos no pueden depender de módulos de negocio.
  • El número de usos de código legado puede bajar, pero un PR que lo aumente falla.
  • Un ciclo nuevo en el grafo de dependencias bloquea el merge.

El tercer punto es el más poderoso: no exige migrar todo de golpe, solo impide que el problema crezca. Cada PR que reduce la cifra baja el umbral, y el progreso queda registrado sin reuniones de seguimiento.

“Una regla de arquitectura que no está automatizada es una sugerencia. Un trinquete en CI la convierte en una propiedad del sistema.”

— Dambert Muñoz

6. La parte humana: dueños, contratos y ritmo

La modularización falla más por organización que por Swift. Tres acuerdos la sostienen:

  • Cada módulo tiene un equipo dueño, declarado en CODEOWNERS, que revisa los cambios en su interfaz pública.
  • Cambiar una interfaz es cambiar un contrato: se anuncia, se depreca y luego se elimina; no se rompe en silencio.
  • La migración es parte del trabajo normal y no un proyecto paralelo: un porcentaje acordado de cada sprint, o la regla de extraer lo que tocas.

Aquí el trabajo de un Staff Engineer no es mover más archivos que nadie. Es conseguir que el roadmap y la migración convivan, detectar cuándo un atajo va a salir caro y mantener visible el progreso con las métricas del primer paso.

7. Cuándo no deberías modularizar

Los módulos tienen costes: más configuración, APIs públicas que diseñar y mantener, y decisiones de versionado. Si el equipo es pequeño, la app compila rápido y los merges no duelen, una buena estructura de carpetas y disciplina con las dependencias pueden ser suficientes. Modularizar sin un problema medible es complejidad que le pides prestada al futuro.

Checklist para empezar el lunes

  • Tengo una línea base: compilación incremental, tiempo de CI y archivos que cambian juntos.
  • Los módulos candidatos siguen capacidades de negocio.
  • Cada capacidad separa interfaz e implementación.
  • La primera extracción cabe en un sprint y llega a producción.
  • Al menos una regla de dependencias se comprueba en CI.
  • Cada módulo tiene un dueño y una política para cambiar su interfaz.

Si estás frente a una migración así y quieres una segunda opinión sobre el plan, revisa la consultoría de arquitectura iOS. Y si quieres practicar cómo defender estas decisiones, el curso de arquitectura de software trabaja justamente eso.

Dambert Muñoz

Escrito por

Staff iOS Architect & AI Architect

Arquitectura de software, productos móviles y formación práctica.