Qué es la arquitectura (y qué no)
"La arquitectura es el conjunto de decisiones costosas de cambiar más adelante." - Ralph Johnson
La arquitectura no es "qué framework usamos" - eso es un detalle de implementación. La arquitectura es la forma que sientes al recorrer la base de código: cómo se dividen los módulos, en qué dirección fluyen las dependencias, qué código puede llamar a cuál.
Una buena arquitectura le dice a un desarrollador dónde escribir la nueva funcionalidad sin ambigüedad. Una mala arquitectura convierte esa pregunta en un debate cada vez.
El coste de las decisiones
El coste real de una decisión arquitectónica no aparece cuando se toma, sino a medida que crece el número de decisiones derivadas. Tres medidas:
- Tiempo de reversión - ¿cuántas semanas para deshacer?
- Superficie de propagación - ¿cuántos archivos / módulos dependen de esta decisión?
- Carga cognitiva - ¿cuántas horas necesita un nuevo desarrollador para entenderla?
Antes de fijar una decisión, pregunta: si dentro de tres meses descubro que está mal, ¿cuán doloroso será revertirla?
Intención frente a implementación
La arquitectura es el conjunto de decisiones que pretendías tomar; el código es lo que realmente se construyó. La distancia entre ambos es la deriva arquitectónica, y crece en silencio hasta que un cambio que debería tomar una hora tarda una semana.
El trabajo no es dibujar el diagrama perfecto una vez. Es mantener la intención y la implementación lo bastante cerca como para que el diagrama siga prediciendo cómo se comporta el código.
En capas (n-niveles)
La división clásica: presentación → aplicación → dominio → datos. Cada capa solo puede llamar a la de debajo. Es el modelo más fácil de enseñar y el más fácil de violar: una llamada de la interfaz directa a la capa de datos y el sistema de capas es pura ficción.
Úsalo cuando el equipo es pequeño y el dominio es superficial. Su debilidad es que la lógica de negocio tiende a filtrarse hacia arriba a los controladores y hacia abajo a la base de datos.
Hexagonal · Puertos y adaptadores
El dominio está en el centro y no sabe nada del mundo exterior. Todo lo externo -base de datos, HTTP, cola de mensajes- le habla a través de puertos (interfaces) que implementan los adaptadores.
La recompensa es la capacidad de prueba y de reemplazo: puedes cambiar Postgres por un doble en memoria en las pruebas, o REST por gRPC, sin tocar una línea del código de dominio.
Clean Architecture
Un refinamiento de la hexagonal: anillos concéntricos donde las dependencias solo apuntan hacia dentro, hacia las entidades del dominio. Frameworks, interfaz y bases de datos viven en el anillo más externo: detalles, no el núcleo.
Te da un dominio que compila y se prueba sin infraestructura alguna. El coste es la ceremonia: más interfaces, más mapeo. Vale la pena para sistemas longevos, exagerado para un panel CRUD.
DDD - Diseño guiado por el dominio
DDD no es un framework - es una disciplina de vocabulario. "Aggregate", "bounded context", "anti-corruption layer" - un lenguaje común para hablar del problema.
¿Cuándo DDD?
- El dominio es complejo, a menudo confundido con la complejidad técnica.
- Varios equipos trabajan en el mismo dominio.
- El negocio y la ingeniería hablan idiomas diferentes.
DDD no es para todos los proyectos. Si resulta pesado para un panel de administración con mucho CRUD, probablemente lo sea.
Gran bola de barro
Sin arquitectura discernible: todo depende de todo, el estado se comparte globalmente y la única forma de entender un cambio es ejecutarlo. La mayoría de los sistemas llegan aquí no por decisión sino por mil fechas límite.
Una bola de barro no se reescribe: se estrangula. Envuelve una costura, mueve una capacidad detrás de una interfaz limpia, repite. El mapa de dependencias de AppArch te muestra dónde están ya las costuras.
Antipatrón: módulo dios
Un "módulo dios" es el que se llama desde todas partes y contiene todo. Nombres como Utils, Common, Shared son señales de advertencia.
# una regla que lo detecta - id: GOD-001 max_fan_in: 25 message: "Module imported by >25 files - split or rename"
Solución: dividir en aproximadamente tres. El grupo uno es infraestructura real (logger, config). El grupo dos son primitivas de dominio (money, id). El grupo tres es probablemente un módulo separado escondido ahí.
Dependencias circulares
El módulo A necesita a B, B necesita a A. Ahora ninguno puede entenderse, probarse ni desplegarse por separado, y el orden de compilación se vuelve una moneda al aire. Los ciclos son el predictor más fiable de un módulo que nadie quiere tocar.
Rompe el ciclo extrayendo el concepto compartido a un tercer módulo del que ambos dependan, o invirtiendo una dirección con una interfaz. AppArch marca los ciclos en cuanto aparecen en una merge request.
Abstracciones con fugas
Una abstracción tiene fugas cuando hay que saber cómo está implementada para usarla bien: un repositorio que lanza excepciones SQL, una caché que obliga a quien llama a gestionar la obsolescencia. Prometió ocultar un detalle y no lo hizo.
O sellas la fuga -traduciendo los errores de implementación en el límite- o eliminas la abstracción. Una con fugas es peor que ninguna: pagas la indirección sin obtener el aislamiento.
Revisión de arquitectura
Una revisión no es una barrera que bloquea merges; es una conversación sobre si un cambio acerca o aleja al sistema de su forma prevista. Las mejores revisiones preguntan «¿qué dificulta esto más adelante?» más que «¿es correcto ahora?»
Mantenla ligera y continua. Una revisión breve en cada merge request importante supera a un comité de arquitectura trimestral que todos esquivan.
Registros de Decisiones de Arquitectura
Un ADR captura una decisión: el contexto, las opciones consideradas, la elección y sus consecuencias - uno o dos párrafos, versionados junto al código. Seis meses después responde la única pregunta que importa: ¿por qué es así?
Escribe el ADR cuando se toma la decisión, no después. Reemplaza en lugar de borrar: el rastro de por qué cambiaste de opinión vale tanto como la respuesta actual.
Refactorización a gran escala
Una gran refactorización que llega como un único merge gigante es una reescritura disfrazada, y se revierte la primera vez que se rompe. La refactorización real es una secuencia de pasos pequeños que preservan el comportamiento, cada uno entregable por sí solo.
Añade el nuevo camino junto al viejo, migra a quienes llaman de forma incremental y luego borra el viejo. Las pruebas son la cuerda de seguridad; sin ellas no refactorizas, apuestas.