En el artículo anterior resumimos la modularización como «agrupar lo que cambia junto para establecer límites».
Pero el verdadero problema empieza después de dividir los módulos. Las piezas comienzan a referenciarse entre sí.
A usa B, B usa C y un día C vuelve a usar A. Todos los beneficios desaparecen porque los tres forman, en la práctica, una sola unidad.
La respuesta breve: hay dos principios. Mantener el flujo de dependencias en una sola dirección y hacer que lo que cambia a menudo dependa de lo estable.
Este artículo resume cómo elegir la dirección, las rutas típicas de las dependencias circulares y tres formas de romperlas.
¿En qué dirección deben fluir las dependencias?
Las dependencias tienen dirección. Si el módulo A importa el módulo B, A depende de B.
Existe una respuesta clásica sobre hacia dónde debe apuntar la flecha.
Lo inestable debe depender de lo estable. Si ocurre lo contrario, un cambio menor se propaga por todo el sistema.
Un módulo estable cambia poco; por ejemplo, los modelos de dominio y los protocolos compartidos.
Un módulo inestable cambia con frecuencia: la UI y la lógica de respuesta a eventos que se modifica según los requisitos.
Por eso, un grafo de dependencias saludable suele apuntar así.
- Módulos de pantalla y funcionalidad → módulos de dominio → módulos de interfaces compartidas
- Las flechas solo bajan hacia lo estable; nunca regresan hacia arriba.
Robert C. Martin llamó a esto principio de dependencias estables (SDP, Stable Dependencies Principle).
¿Por qué aparecen las dependencias circulares?
La mayoría surge de forma natural y sin mala intención. Una ruta típica es la siguiente.
El módulo Order referencia al módulo User porque un pedido necesita los datos de quien lo realizó.
Con el tiempo llega el requisito de mostrar «pedidos recientes» en la pantalla de usuario. Cuando User referencia a Order, el ciclo queda cerrado.
Cuando aparece un ciclo, se rompen tres cosas.
- Falla la separación de unidades de compilación: el compilador no puede procesarlas por separado y, en la práctica, se vuelven un módulo (Swift bloquea los import circulares entre módulos con un error de compilación).
- No se pueden hacer pruebas independientes: probar A requiere B y probar B requiere A; un bloqueo mutuo.
- El alcance del impacto es impredecible: cualquier cambio exige revisar también el lado opuesto.
¿Cómo se rompen las dependencias circulares?
En la práctica hay tres métodos principales.
Primero, mover las piezas comunes hacia abajo.
Si ambos lados solo necesitan «una parte del otro», extráela a un módulo inferior más estable. Por ejemplo, lleva al módulo de modelos de dominio solo los tipos que necesitan Order y User.
Segundo, invertir la dirección mediante interfaces (DIP).
Sustituye una dependencia por un protocolo. User define solo el protocolo «algo que proporciona una lista de pedidos», y Order aporta la implementación.
// User Módulo: declarar como protocolo solo las capacidades necesarias
public protocol OrderHistoryProviding {
func recentOrders(of userID: String) -> [OrderSummary]
}
// Order Módulo: User Adoptar e implementar el protocolo del módulo
public struct OrderHistoryProvider: OrderHistoryProviding {
public func recentOrders(of userID: String) -> [OrderSummary] {
// Consultar el repositorio de pedidos
}
}
Así solo queda una flecha: Order → User. La inversión de dependencias del artículo anterior sobre DIP se aplica directamente a nivel de módulo.
Tercero, subir el ensamblaje.
En lugar de conectar directamente los módulos, los conecta un módulo superior que conoce ambos (el destino de la app o la capa de ensamblaje). Es especialmente útil cuando los módulos funcionales deben invocarse para cambiar de pantalla.
| Situación | Cómo romperla |
|---|---|
| Cada lado solo necesita parte de los tipos del otro | Extraer los tipos comunes a un módulo inferior |
| Un lado llama a la funcionalidad del otro | Definir un protocolo e invertir la dependencia |
| Navegación entre módulos funcionales | Conectarlos en una capa superior de ensamblaje |
¿Cuándo aplicarlo y cuándo es excesivo?
Gestionar la dirección de las dependencias también tiene un coste: aumentan los protocolos y el código de ensamblaje.
- Con 3 o 4 módulos o más y equipos separados, merece la pena documentar las reglas y vigilar los ciclos con herramientas.
- En un proyecto pequeño de dos módulos, basta con evitar los ciclos. Envolver cada referencia en un protocolo es excesivo.
- En un sistema heredado que ya tiene ciclos, no intentes romperlos todos de una vez; empieza por el módulo que cambia con más frecuencia.
Así lo preguntan en una entrevista
P. ¿Por qué son problemáticas las dependencias circulares entre módulos y cómo las resolverías?
Un ciclo convierte dos módulos en una sola unidad para compilación, pruebas y despliegue, eliminando los beneficios de la modularización. Extrae los tipos comunes a un módulo inferior, invierte la dirección con un protocolo (DIP) o conecta ambos módulos en una capa superior de ensamblaje para dejar las flechas en una sola dirección.
P. Explica el principio de dependencias estables (SDP).
Un módulo solo debe depender de módulos más estables que él, es decir, que cambien menos. Si un módulo que cambia a menudo queda aguas abajo, sus cambios se propagan aguas arriba; por eso los módulos de dominio e interfaz, de baja frecuencia de cambio, deben quedar abajo en el grafo.
El próximo artículo trata una trampa común durante la modularización: el módulo Common, peligroso ya desde el nombre.
Veremos cómo «como es compartido, pongámoslo en Common» devuelve todo el sistema a una sola unidad y cómo prevenirlo con una estructura por capas.

![Imagen de portada de [Modularización #2] Dependencias entre módulos: diseño y ciclos](/assets/images/posts/43006932-2be1-4618-af56-bd9ceee6264e/1.jpg)