Módulo 3: Entender Arquitectura Existente
Dependency Maps y Grafos de Dependencias
Dependency Maps y Grafos de Dependencias
Descripción de la capsula
Vas a aprender a generar dependency maps — representaciones visuales y textuales de qué módulo depende de qué en un codebase. Este es el primer artefacto de tu architecture map y el más fundamental: sin saber las dependencias, no puedes trazar flujos, no puedes identificar patterns, y no puedes predecir el impacto de cambios.
Un dependency map responde la pregunta más importante antes de cualquier refactoring: "Si cambio este módulo, ¿qué otros módulos se afectan?" La respuesta a esa pregunta determina el riesgo, el esfuerzo, y la prioridad de cualquier cambio. Sin dependency map, estimas a ojo. Con dependency map, decides con datos.
En esta cápsula aprenderás a usar Claude Code para generar dependency maps a tres niveles de zoom — desde la vista de pájaro de 3-5 componentes principales hasta el detalle de funciones individuales. Aprenderás a leer e interpretar esos mapas, detectar patterns problemáticos como circular dependencies y high fan-out, y producir artefactos que otro developer puede usar para entender la estructura del sistema.
Que Es un Dependency Map
La idea fundamental
Un dependency map es un grafo donde:
- Nodos = módulos, archivos, clases, o funciones
- Aristas = relaciones de dependencia (imports, llamadas, herencia)
Si user_service.py importa user_repository.py, hay una arista de user_service hacia user_repository. Eso significa que user_service depende de user_repository — si cambias user_repository, user_service podría romperse.
Que muestra un dependency map
Lo que VES: Lo que SIGNIFICA:
user_service -> user_repository "user_service usa user_repository"
"Si cambias user_repository,
revisa user_service"
auth_middleware -> user_service "auth necesita user_service"
"No puedes eliminar user_service
sin romper auth"
payment_service -> user_service "Payments consulta users"
payment_service -> email_service "Payments envía emails"
"payment_service tiene fan-out de 2"
Por que importa
Sin dependency map:
Developer: "Voy a refactorizar user_repository"
Resultado: 💥 Se rompen auth, payments, notifications, y admin
Developer: "No sabía que 4 módulos dependían de esto"
Con dependency map:
Developer: "Voy a refactorizar user_repository"
Mapa: user_repository es importado por: user_service,
auth_service, payment_service, admin_service
Developer: "OK, necesito actualizar 4 módulos. Empiezo
por el que tiene menos dependencias propias."
Tres Niveles de Zoom
El error más común al crear dependency maps es intentar mostrar todo a la vez. Un mapa con 50 módulos y 200 flechas no es un mapa — es ruido. La solución es trabajar con tres niveles de zoom.
Nivel 1: High-Level (3-5 componentes principales)
Este nivel responde: "¿Cuáles son los grandes bloques del sistema y cómo se conectan?"
Tú a Claude Code:
> "Analiza la estructura de este proyecto. Identifica los 3-5
> componentes principales (no módulos individuales, sino grupos
> de módulos que cumplen una función). Muestra las dependencias
> entre esos componentes como un diagrama Mermaid."
Claude Code responde:
graph TD
API["API Layer<br/>routes/, middleware/"]
SERVICES["Business Logic<br/>services/"]
DATA["Data Access<br/>repositories/, models/"]
EXTERNAL["External Services<br/>clients/"]
CONFIG["Configuration<br/>config/, utils/"]
API --> SERVICES
API --> CONFIG
SERVICES --> DATA
SERVICES --> EXTERNAL
SERVICES --> CONFIG
DATA --> CONFIG
EXTERNAL --> CONFIG
Cómo leer este diagrama:
- Las flechas van de "depende de" hacia "es usado por"
- API depende de Services y Config
- Services depende de Data, External, y Config
- Config no depende de nadie — es la base
Cuándo usar Nivel 1:
- Para explicar el sistema a alguien nuevo
- Para entender la big picture antes de profundizar
- Para verificar que la arquitectura tiene layers claros
Nivel 2: Medium (módulos dentro de cada componente)
Este nivel responde: "¿Qué módulos hay dentro de cada componente y cómo se conectan?"
Tú a Claude Code:
> "Ahora profundiza en el componente 'Services'. Lista todos
> los módulos en services/ y muestra las dependencias entre
> ellos. Incluye también qué módulos de otros componentes
> importan. Formato: diagrama Mermaid."
Claude Code responde:
graph TD
subgraph Services
US["user_service.py"]
PS["payment_service.py"]
NS["notification_service.py"]
AS["auth_service.py"]
OS["order_service.py"]
end
subgraph Repositories
UR["user_repository.py"]
PR["payment_repository.py"]
OR["order_repository.py"]
end
subgraph External
EC["email_client.py"]
SC["stripe_client.py"]
end
US --> UR
PS --> PR
PS --> US
PS --> SC
NS --> EC
NS --> US
AS --> US
AS --> UR
OS --> OR
OS --> US
OS --> PS
Cómo leer este diagrama:
payment_servicedepende deuser_service,payment_repository, ystripe_client— tiene fan-out de 3user_servicees importado porpayment_service,notification_service,auth_service, yorder_service— tiene fan-in de 4user_servicees el hub: el módulo más conectado del sistema
Cuándo usar Nivel 2:
- Para planificar un refactoring específico
- Para entender el impacto de cambiar un módulo
- Para identificar módulos con demasiadas dependencias
Nivel 3: Low (funciones dentro de un módulo)
Este nivel responde: "¿Qué funciones tiene este módulo y cuáles son llamadas desde fuera?"
Tú a Claude Code:
> "Analiza user_service.py. Lista todas las funciones públicas
> y privadas. Para cada función pública, muestra qué otros
> módulos la llaman. Formato: tabla."
Claude Code responde:
| Función | Tipo | Llamada desde | Descripción |
|---|---|---|---|
get_user(user_id) | Pública | auth_service, order_service, admin_routes | Obtiene usuario por ID |
create_user(data) | Pública | auth_service, user_routes | Crea nuevo usuario |
update_user(user_id, data) | Pública | user_routes, admin_routes | Actualiza datos de usuario |
delete_user(user_id) | Pública | admin_routes | Elimina usuario (soft delete) |
get_user_with_orders(user_id) | Pública | order_service, admin_routes | Usuario con sus pedidos |
_validate_email(email) | Privada | (interna) | Valida formato de email |
_hash_password(password) | Privada | (interna) | Hash con bcrypt |
_check_duplicates(email) | Privada | (interna) | Verifica email único |
Cuándo usar Nivel 3:
- Para entender exactamente qué funciones son la "API pública" de un módulo
- Para decidir si dividir un módulo grande
- Para encontrar funciones que son privadas pero deberían ser públicas (o viceversa)
Generar Dependency Maps con Claude Code
Prompt básico: overview de dependencias
Tú a Claude Code:
> "Analiza las dependencias de este proyecto. Para cada archivo
> Python en src/, lista qué otros archivos del proyecto importa.
> No incluyas dependencias externas (pip packages), solo imports
> internos del proyecto. Formato: lista organizada por directorio."
Claude Code responde:
src/api/
auth_routes.py -> services/auth_service.py, middleware/auth.py
user_routes.py -> services/user_service.py, schemas/user.py
order_routes.py -> services/order_service.py, schemas/order.py
src/services/
auth_service.py -> repositories/user_repository.py, utils/jwt.py
user_service.py -> repositories/user_repository.py, utils/validators.py
order_service.py -> repositories/order_repository.py, services/user_service.py
payment_service.py -> services/user_service.py, clients/stripe_client.py
src/repositories/
user_repository.py -> models/user.py, config/database.py
order_repository.py -> models/order.py, models/user.py, config/database.py
src/models/
user.py -> config/database.py
order.py -> config/database.py, models/user.py
Prompt para mermaid diagram
Tú a Claude Code:
> "Genera un diagrama Mermaid de dependencias para los archivos
> en src/services/. Muestra cada archivo como un nodo y cada
> import interno como una flecha. Agrupa los nodos por directorio
> usando subgraphs."
Output esperado:
graph TD
subgraph API
AR["auth_routes"]
URR["user_routes"]
ORR["order_routes"]
end
subgraph Services
AS["auth_service"]
US["user_service"]
OS["order_service"]
PS["payment_service"]
end
subgraph Repositories
URep["user_repository"]
ORep["order_repository"]
end
AR --> AS
URR --> US
ORR --> OS
AS --> URep
US --> URep
OS --> ORep
OS --> US
PS --> US
Prompt para detectar metricas clave
Tú a Claude Code:
> "Calcula las siguientes métricas de dependencias para cada
> archivo Python en src/:
> 1. Fan-out: cuántos módulos internos importa
> 2. Fan-in: cuántos módulos internos lo importan
> 3. Ratio fan-in/fan-out
> Ordena por fan-in descendente. Formato: tabla."
Claude Code responde:
| Archivo | Fan-out | Fan-in | Ratio | Rol |
|---------|---------|--------|-------|-----|
| user_service.py | 2 | 4 | 2.0 | Hub |
| user_repository.py | 2 | 3 | 1.5 | Hub |
| config/database.py | 0 | 5 | ∞ | Fundación |
| order_service.py | 3 | 1 | 0.3 | Consumer |
| payment_service.py | 2 | 0 | 0.0 | Endpoint |
Cómo interpretar las métricas:
- Fan-in alto = muchos módulos dependen de él. Cambios aquí tienen alto impacto. Manejar con cuidado.
- Fan-out alto = depende de muchos módulos. Posible god object o responsabilidades mixtas.
- Fan-in alto + Fan-out bajo = módulo fundacional. Estable, muchos lo usan, pero él es independiente.
- Fan-out alto + Fan-in bajo = módulo coordinador o posible candidato para dividir.
- Fan-in = 0 = nadie lo importa. Posible dead code o entry point.
Detectar Patterns Problematicos
Circular dependencies
Una dependencia circular ocurre cuando A depende de B y B depende de A (directa o indirectamente).
Tú a Claude Code:
> "Busca dependencias circulares en este proyecto. Una dependencia
> circular es cuando el módulo A importa B, y B importa A (directa
> o indirectamente a través de otros módulos). Lista todas las
> que encuentres."
Claude Code responde:
⚠️ Dependencia circular detectada:
Ciclo 1: auth_service.py -> user_service.py -> auth_service.py
- auth_service importa user_service (para get_user en login)
- user_service importa auth_service (para hash_password en create)
Ciclo 2: order_service.py -> payment_service.py -> order_service.py
- order_service importa payment_service (para procesar pago)
- payment_service importa order_service (para actualizar estado)
Por qué son problemáticas:
- Hacen difícil entender qué depende de qué
- Dificultan testing: no puedes testear A sin B, ni B sin A
- Complican refactoring: no puedes mover A sin mover B
- En Python, pueden causar
ImportErrorsi no se manejan con lazy imports
Cómo se resuelven (preview del Módulo 4):
- Extract common interface: sacar lo compartido a un tercer módulo
- Dependency inversion: crear una abstracción que ambos implementen
- Event-based communication: A emite un evento, B lo escucha (sin import directo)
High fan-out (demasiadas dependencias)
Tú a Claude Code:
> "Identifica los archivos con mayor fan-out (que importan más
> módulos internos del proyecto). Si alguno importa más de 5
> módulos diferentes, márcalo como potencial problema."
Claude Code responde:
⚠️ High fan-out detectado:
order_service.py - Fan-out: 7
Importa: user_service, payment_service, notification_service,
order_repository, product_service, shipping_service,
config
Análisis: Este módulo coordina demasiadas responsabilidades.
Cada cambio en cualquiera de los 7 módulos puede afectarlo.
Candidato para dividir en módulos más pequeños.
Cómo interpretar:
- Fan-out > 5 es una señal de alerta (no una regla rígida)
- Pregunta clave: "¿Este módulo tiene una responsabilidad clara, o está haciendo demasiadas cosas?"
- Si es un coordinador legítimo (como un orchestrator), el fan-out puede ser aceptable
- Si mezcla lógica de negocio con coordinación, es candidato a split
High fan-in sin abstracción
Tú a Claude Code:
> "Identifica los archivos con mayor fan-in (que son importados
> por más módulos). Si alguno tiene fan-in > 5 y no es un módulo
> de configuración o utilidad, márcalo."
Claude Code responde:
📊 Fan-in alto:
user_service.py - Fan-in: 8
Importado por: auth_routes, user_routes, admin_routes,
order_service, payment_service,
notification_service, auth_service,
reporting_service
Análisis: Este es el hub del sistema. Cualquier cambio en
su interfaz pública afecta 8 módulos. Si no tiene una
interfaz estable y bien definida, cada cambio es riesgoso.
Cómo interpretar:
- Fan-in alto no es inherentemente malo — los módulos fundacionales tienen fan-in alto legítimamente
- El problema es cuando el módulo cambia frecuentemente Y tiene fan-in alto
- Pregunta clave: "¿La interfaz pública de este módulo es estable?"
Dependency Maps con Código Python
A veces necesitas generar dependency maps programáticamente. Aquí hay un approach con Claude Code que combina análisis y código:
Generar dependency map automatizado
Tú a Claude Code:
> "Escribe un script Python que analice los imports internos
> de todos los archivos .py en src/. Para cada archivo, extrae
> las líneas 'from ... import' y 'import ...' que referencien
> otros archivos del proyecto (no pip packages). Output: JSON
> con las dependencias."
Claude Code genera:
# dependency_analyzer.py
import ast
import os
import json
from pathlib import Path
def get_internal_imports(file_path: str, project_root: str) -> list[str]:
"""
Extrae imports internos de un archivo Python.
Solo incluye imports que referencien otros archivos del proyecto.
"""
try:
with open(file_path, "r") as f:
tree = ast.parse(f.read())
except SyntaxError:
return []
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
if node.module and not _is_external(node.module, project_root):
imports.append(node.module)
elif isinstance(node, ast.Import):
for alias in node.names:
if not _is_external(alias.name, project_root):
imports.append(alias.name)
return imports
def _is_external(module_name: str, project_root: str) -> bool:
"""Determina si un import es externo (pip package) o interno."""
# Convierte dots a path: "services.user_service" -> "services/user_service"
module_path = module_name.replace(".", "/")
# Busca si existe como archivo o directorio en el proyecto
for ext in [".py", "/__init__.py", ""]:
candidate = os.path.join(project_root, "src", module_path + ext)
if os.path.exists(candidate):
return False
return True
def build_dependency_map(project_root: str) -> dict:
"""
Construye el mapa completo de dependencias internas.
Returns: {archivo: [lista de archivos de los que depende]}
"""
src_dir = os.path.join(project_root, "src")
dependency_map = {}
for py_file in Path(src_dir).rglob("*.py"):
if "__pycache__" in str(py_file):
continue
relative_path = str(py_file.relative_to(src_dir))
imports = get_internal_imports(str(py_file), project_root)
if imports:
dependency_map[relative_path] = imports
return dependency_map
def calculate_metrics(dep_map: dict) -> dict:
"""Calcula fan-in y fan-out para cada módulo."""
all_modules = set(dep_map.keys())
# Agregar módulos que son importados pero no tienen imports propios
for imports in dep_map.values():
for imp in imports:
module_file = imp.replace(".", "/") + ".py"
all_modules.add(module_file)
metrics = {}
for module in all_modules:
fan_out = len(dep_map.get(module, []))
fan_in = sum(
1 for imports in dep_map.values()
if any(imp.replace(".", "/") + ".py" == module for imp in imports)
)
metrics[module] = {
"fan_out": fan_out,
"fan_in": fan_in,
"ratio": round(fan_in / fan_out, 2) if fan_out > 0 else float("inf")
}
return dict(sorted(
metrics.items(),
key=lambda x: x[1]["fan_in"],
reverse=True
))
if __name__ == "__main__":
import sys
project_root = sys.argv[1] if len(sys.argv) > 1 else "."
dep_map = build_dependency_map(project_root)
print("=== Dependency Map ===")
print(json.dumps(dep_map, indent=2))
print("\n=== Metrics ===")
metrics = calculate_metrics(dep_map)
print(f"\n{'Module':<40} {'Fan-out':>8} {'Fan-in':>7} {'Ratio':>7}")
print("-" * 65)
for module, m in metrics.items():
ratio_str = f"{m['ratio']:.1f}" if m['ratio'] != float('inf') else "∞"
print(f"{module:<40} {m['fan_out']:>8} {m['fan_in']:>7} {ratio_str:>7}")
# Output esperado:
# === Dependency Map ===
# {
# "services/user_service.py": ["repositories.user_repository", "utils.validators"],
# "services/order_service.py": ["repositories.order_repository", "services.user_service"],
# ...
# }
#
# === Metrics ===
# Module Fan-out Fan-in Ratio
# -----------------------------------------------------------------
# services/user_service.py 2 4 2.0
# repositories/user_repository.py 2 3 1.5
# config/database.py 0 5 ∞
Generar mermaid desde el analysis
Tú a Claude Code:
> "Toma el dependency map que generamos y conviértelo en un
> diagrama Mermaid. Agrupa los módulos por directorio usando
> subgraphs. Solo incluye dependencias con fan-in > 1 para
> mantener el diagrama legible."
Esto produce un diagrama filtrado que muestra solo las relaciones más importantes, no todo el ruido de dependencias menores.
Comparacion: Análisis Manual vs Claude Code
| Aspecto | Análisis Manual | Con Claude Code |
|---|---|---|
| Tiempo para proyecto de 10K líneas | 4-8 horas | 15-30 minutos |
| Precisión | Humana (se olvidan imports indirectos) | Completa (analiza todos los archivos) |
| Actualización | Repetir todo el proceso | Re-ejecutar el prompt |
| Formato de output | Depende del tool (pizarrón, draw.io) | Texto + Mermaid (versionable) |
| Detección de circulares | Muy difícil manualmente | Automático |
| Métricas (fan-in/fan-out) | Tedioso de calcular | Instantáneo |
| Diferentes zoom levels | Hay que redibujar | Nuevo prompt, nuevo nivel |
El trade-off:
- Claude Code es más rápido y completo para la generación
- El humano es mejor para la interpretación y las decisiones
- El workflow ideal: Claude Code genera -> tú interpretas -> Claude Code ajusta basándose en tu feedback
Conexión con Proyecto
En el Architecture Map de Proyecto Real (cápsula 05), el dependency map es el primer componente que generarás.
Usarás las técnicas de esta cápsula para:
- Generar el dependency map a Nivel 1 (high-level) y Nivel 2 (medium) del proyecto elegido
- Calcular métricas de fan-in y fan-out
- Detectar dependencias circulares y high fan-out
- Documentar los hallazgos con diagramas Mermaid
Todo lo que aprendes hoy se aplica directamente en la cápsula 05.
Y en el Módulo 4 (Refactoring), los dependency maps que generes aquí te dirán exactamente qué módulos tocar y en qué orden refactorizar.
Troubleshooting
Problema 1: Claude Code genera un diagrama demasiado complejo
Causa: El proyecto tiene muchos módulos y Claude Code intenta mostrar todos. Solución:
Tú a Claude Code:
> "El diagrama tiene demasiados nodos. Simplifica:
> 1. Agrupa archivos por directorio (un nodo por directorio)
> 2. Solo muestra dependencias entre directorios, no entre
> archivos individuales
> 3. Máximo 8 nodos en el diagrama"
Problema 2: Claude Code incluye dependencias externas (pip packages)
Causa: Claude Code no distingue correctamente entre imports internos y externos. Solución:
Tú a Claude Code:
> "Solo incluye dependencias INTERNAS del proyecto. Excluye:
> - Cualquier import de la standard library (os, sys, json, etc.)
> - Cualquier import de pip packages (fastapi, sqlalchemy, etc.)
> - Solo muestra imports que referencien archivos dentro de src/"
Problema 3: No detecta dependencias indirectas
Causa: Solo miras imports directos, pero A depende de B que depende de C (A depende indirectamente de C). Solución:
Tú a Claude Code:
> "Genera el dependency map incluyendo dependencias transitivas.
> Si A importa B y B importa C, muestra que A depende
> indirectamente de C. Usa líneas punteadas para dependencias
> indirectas y sólidas para directas."
Problema 4: El diagrama Mermaid no renderiza correctamente
Causa: Caracteres especiales en nombres de archivos o syntax errors en Mermaid. Solución:
Tú a Claude Code:
> "El diagrama Mermaid no renderiza. Revisa:
> 1. Que los IDs de nodos no tengan puntos ni guiones
> 2. Que los labels estén entre comillas si tienen espacios
> 3. Que no haya nodos huérfanos
> Genera una versión corregida."
Problema 5: No se como interpretar las metricas
Causa: Los números de fan-in/fan-out no tienen significado sin contexto. Solución: Usa estas heurísticas como punto de partida:
| Métrica | Normal | Atención | Problema |
|---|---|---|---|
| Fan-out | 1-3 | 4-6 | 7+ |
| Fan-in | 1-5 | 6-10 | 11+ |
| Circular deps | 0 | 1 | 2+ |
Recuerda: estas son heurísticas, no reglas. Un módulo config.py con fan-in de 15 puede ser perfectamente normal.
Ejercicios
Ejercicio 1: Dependency Map básico (Fácil)
Dado este código de ejemplo, identifica todas las dependencias internas y dibuja el dependency map:
# Archivo: routes/user_routes.py
from services.user_service import UserService
from schemas.user import UserCreate, UserResponse
# Archivo: services/user_service.py
from repositories.user_repository import UserRepository
from utils.validators import validate_email
# Archivo: repositories/user_repository.py
from models.user import User
from config.database import get_session
# Archivo: services/auth_service.py
from services.user_service import UserService
from utils.jwt import create_token
Dibuja el dependency map como texto (flechas ASCII) y calcula el fan-in y fan-out de cada módulo.
Ver solución
Dependency map:
routes/user_routes -> services/user_service
routes/user_routes -> schemas/user
services/user_service -> repositories/user_repository
services/user_service -> utils/validators
repositories/user_repository -> models/user
repositories/user_repository -> config/database
services/auth_service -> services/user_service
services/auth_service -> utils/jwt
Métricas:
| Módulo | Fan-out | Fan-in |
|---|---|---|
| routes/user_routes | 2 | 0 |
| services/user_service | 2 | 2 |
| services/auth_service | 2 | 0 |
| repositories/user_repository | 2 | 1 |
| schemas/user | 0 | 1 |
| utils/validators | 0 | 1 |
| models/user | 0 | 1 |
| config/database | 0 | 1 |
| utils/jwt | 0 | 1 |
Observaciones:
services/user_servicees el hub (fan-in = 2, fan-out = 2)- Los módulos con fan-in = 0 son entry points o módulos leaf
- Los módulos con fan-out = 0 son fundacionales (no dependen de nada interno)
Explicación: El dependency map se construye siguiendo cada import. El fan-out es cuántos módulos importa, el fan-in es cuántos lo importan a él.
Ejercicio 2: Detectar dependencia circular (Fácil)
Observa estos imports y determina si hay dependencias circulares:
# Archivo: services/order_service.py
from services.payment_service import process_payment
from services.user_service import get_user
# Archivo: services/payment_service.py
from services.order_service import update_order_status
from clients.stripe import charge_card
# Archivo: services/user_service.py
from repositories.user_repository import UserRepository
# Archivo: services/notification_service.py
from services.user_service import get_user
from services.order_service import get_order
Ver solución
Sí hay una dependencia circular:
order_service -> payment_service -> order_service ⚠️ CIRCULAR
order_serviceimportapayment_service(paraprocess_payment)payment_serviceimportaorder_service(paraupdate_order_status)
No hay circular en:
user_serviceno importa ningún otro service (es unidireccional)notification_serviceimportauser_serviceyorder_service, pero ninguno lo importa a él
Cómo resolver la circular (preview):
- Extraer
update_order_statusa un módulo compartido - Usar eventos:
payment_serviceemite un evento "payment_completed" yorder_servicelo escucha - Dependency inversion: crear una interfaz que ambos usen
Explicación: La circular se detecta trazando las flechas. Si puedes seguir una ruta que te lleva de vuelta al punto de partida, hay un ciclo.
Ejercicio 3: Análisis con Claude Code (Medio)
Usa Claude Code para analizar las dependencias de un proyecto real. Elige un proyecto Python que tengas disponible (puede ser el que usaste en los Módulos 1-2) y ejecuta estos prompts:
- Prompt para listar todos los imports internos
- Prompt para generar el diagrama Mermaid a Nivel 1
- Prompt para calcular fan-in y fan-out
Documenta los prompts exactos que usaste, las respuestas de Claude Code, y tu interpretación.
Ver solución
Ejemplo con un proyecto FastAPI:
Prompt 1:
> "Lista todos los archivos Python en src/ y para cada uno,
> muestra los imports internos (no pip packages, no stdlib).
> Formato: archivo -> [lista de imports]"
Prompt 2:
> "Genera un diagrama Mermaid de las dependencias a nivel de
> directorio. Cada directorio es un nodo. Las flechas muestran
> si archivos de un directorio importan archivos de otro."
Prompt 3:
> "Para cada archivo Python en src/, calcula:
> - Fan-out: cuántos archivos internos importa
> - Fan-in: cuántos archivos internos lo importan
> Ordena por fan-in descendente. Formato tabla."
Tu interpretación debería responder:
- ¿Cuál es el módulo más conectado? (mayor fan-in)
- ¿Hay algún módulo con fan-out sospechosamente alto?
- ¿Hay dependencias circulares?
- ¿La estructura de capas (layers) se respeta? (API -> Services -> Repositories)
Explicación: Este ejercicio te obliga a practicar el workflow completo: prompt -> output -> interpretación. No hay una única respuesta correcta — depende del proyecto que elijas.
Ejercicio 4: Interpretar metricas (Medio)
Dado este reporte de métricas, identifica los problemas potenciales y sugiere acciones:
Module Fan-out Fan-in Ratio
-----------------------------------------------------------------
services/user_service.py 2 8 4.0
services/order_orchestrator.py 9 1 0.1
config/settings.py 0 12 ∞
services/payment_service.py 3 3 1.0
services/auth_service.py 4 6 1.5
utils/helpers.py 0 7 ∞
models/base.py 0 10 ∞
services/legacy_handler.py 6 0 0.0
Ver solución
Problemas identificados:
-
order_orchestrator.py— Fan-out de 9 ⚠️- Depende de 9 módulos diferentes
- Posible god object o módulo con demasiadas responsabilidades
- Acción: Investigar si se puede dividir en orquestadores más pequeños
-
user_service.py— Fan-in de 8 ⚠️- 8 módulos dependen de él
- Cualquier cambio en su interfaz pública afecta a 8 consumidores
- Acción: Asegurar que su interfaz pública sea estable y bien definida
-
legacy_handler.py— Fan-out de 6, Fan-in de 0 ⚠️- Depende de 6 módulos pero nadie lo importa
- Posible dead code o entry point no documentado
- Acción: Verificar si realmente se usa (puede ser llamado dinámicamente o desde tests)
-
config/settings.pyymodels/base.py— Fan-in alto, fan-out 0 ✅- Esto es normal: son módulos fundacionales que muchos necesitan
- No requieren acción
-
utils/helpers.py— Fan-in de 7 ⚠️- Un módulo "helpers" con fan-in alto a menudo es un cajón de sastre
- Acción: Investigar si las funciones deberían estar en módulos más específicos
Explicación: Las métricas por sí solas no dicen "hay un problema." Pero combinadas con el contexto (¿qué hace el módulo?) permiten identificar patterns sospechosos y priorizar investigación.
Ejercicio 5: Generar recomendaciones (Difícil)
Dado el siguiente dependency map, genera un reporte de recomendaciones que incluya: (a) problemas detectados, (b) nivel de riesgo de cada uno, (c) acción sugerida para cada problema.
graph TD
A["auth_service"] --> B["user_service"]
B --> A
C["order_service"] --> B
C --> D["payment_service"]
D --> C
E["notification_service"] --> B
E --> C
F["admin_service"] --> B
F --> C
F --> D
F --> E
F --> A
G["reporting_service"] --> B
G --> C
G --> D
Ver solución
Reporte de recomendaciones:
| # | Problema | Riesgo | Acción sugerida |
|---|---|---|---|
| 1 | Circular: auth_service <-> user_service | Alto | Extraer funcionalidad compartida a un módulo auth_utils o usar dependency inversion |
| 2 | Circular: order_service <-> payment_service | Alto | Usar eventos: payment emite "payment_completed", order lo escucha. Rompe la dependencia directa |
| 3 | admin_service tiene fan-out de 5 | Medio | Revisar si es un god object. Posiblemente dividir en admin_user_service, admin_order_service, etc. |
| 4 | user_service tiene fan-in de 5 | Medio | Asegurar interfaz pública estable. Considerar crear una abstracción/interfaz si cambia frecuentemente |
| 5 | reporting_service depende de 3 services directamente | Bajo | Evaluar si debería depender de repositories directamente en vez de services, o usar un read model |
Orden de prioridad para refactoring:
- Resolver circulares (auth <-> user, order <-> payment) — bloquean otros cambios
- Evaluar admin_service — fan-out de 5 es señal de demasiadas responsabilidades
- Estabilizar interfaz de user_service — afecta a 5 módulos
- Evaluar reporting_service — optimización, no urgente
Explicación: Las dependencias circulares son siempre la prioridad más alta porque complican todos los demás cambios. El orden de refactoring se basa en: (1) desbloquear, (2) estabilizar hubs, (3) optimizar.
Resumen
En esta cápsula aprendiste:
- ✅ Qué es un dependency map y por qué es el primer artefacto del architecture map
- ✅ Los tres niveles de zoom: high-level (componentes), medium (módulos), low (funciones)
- ✅ Cómo usar Claude Code para generar dependency maps en texto y Mermaid
- ✅ Métricas clave: fan-in (cuántos te usan), fan-out (de cuántos dependes)
- ✅ Patterns problemáticos: circular dependencies, high fan-out, god objects
- ✅ La diferencia entre análisis manual (horas) y con Claude Code (minutos)
- ✅ Cómo interpretar métricas: los números sin contexto no dicen nada, pero con contexto informan decisiones
Próxima cápsula: Flow Analysis — Request->Response y Data Pipelines — cómo trazar el camino que siguen los datos y requests a través de las dependencias que acabas de mapear.
Recursos Adicionales
- Mermaid Flowchart Syntax — Referencia completa para crear diagramas de flujo y grafos de dependencias en Mermaid
- Python AST Module — Official Docs — Para entender cómo funciona el análisis estático de imports en Python
- Dependency Management in Software Architecture — Martin Fowler sobre dependency inversion y cómo resolver dependencias problemáticas
- Software Design X-Rays — Adam Tornhill — Análisis de dependencias usando datos de versión control como complemento al análisis estático
- Circular Dependencies in Python — Real Python — Guía práctica sobre imports y cómo manejar circulares en Python
- C4 Model — Component Diagram — Inspiración para diferentes niveles de zoom en diagramas de arquitectura
Módulo 3, Cápsula 02 — Refactoring & Legacy Code with Claude Code Guide