Podemos clasificar una aplicación atendiendo a distintos criterios. Estas decisiones se combinan: por ejemplo, una aplicación puede desplegarse como monolito modular y organizarse internamente por capas con inversión de dependencias.
1. Principios de arquitectura limpia
Las arquitecturas limpias protegen las reglas de negocio frente a los detalles técnicos y externos.
Independencia de frameworks: el framework es una herramienta, no el centro de la aplicación.
Testeable: las reglas de negocio pueden probarse sin levantar la interfaz, la base de datos ni el framework.
Independencia de la interfaz de usuario: la misma lógica puede exponerse mediante interfaces distintas.
Independencia de la base de datos: se puede sustituir la tecnología de persistencia sin reescribir las reglas de negocio.
Independencia de agentes externos: las reglas de negocio no dependen de servicios externos concretos.
Regla de dependencia: las dependencias del código apuntan hacia las políticas y reglas de negocio, no hacia los detalles.
Inversión de dependencias (DIP): las abstracciones que necesita el núcleo pertenecen al núcleo; los detalles externos las implementan.
Arquitectura limpia es un concepto general: busca proteger las reglas de negocio y dirigir las dependencias hacia el núcleo. La arquitectura hexagonal, la arquitectura de cebolla y la arquitectura por capas con inversión de dependencias (DIP) son distintos enfoques de arquitectura limpia.
2. Organización interna
2.1 Arquitectura por capas
Organiza el código según responsabilidades. En aplicaciones web es habitual distinguir estas capas:
Capa
Responsabilidad
controller
Recibir solicitudes, coordinar la entrada y producir respuestas
domain
Contener servicios, casos de uso y reglas del negocio
persistence
Implementar el acceso a datos y comunicarse con la base de datos
En una arquitectura por capas tradicional, controller llama a domain y este utiliza persistence. Si el dominio depende directamente de clases concretas de persistencia, queda acoplado a la tecnología.
Cuando domain define las interfaces que necesita y persistence las implementa, se invierte la dependencia respecto a la persistencia y se aplican principios de arquitectura limpia.
Las capas son una división lógica, no una estructura rígida. Se pueden añadir capas o separar componentes según las necesidades; por ejemplo, los casos de uso pueden estar separados de los servicios de dominio.
Sencillo; vigilar el acoplamiento hacia infraestructura
Hexagonal
Aislar el núcleo mediante puertos y adaptadores
Facilita sustituir tecnologías y probar el núcleo
Cebolla
Organizar el sistema en anillos alrededor del dominio
Las dependencias apuntan hacia el centro
3. Arquitectura de despliegue
Describe cómo se empaqueta, ejecuta y despliega la aplicación. Es un criterio distinto de la organización interna.
3.1 Monolito, monolito modular y microservicios
Estilo
Despliegue
Límites y datos
Ventajas y costes
Monolito
Una aplicación desplegable
Los componentes suelen compartir proceso y almacenamiento
Operación sencilla; puede crecer con alto acoplamiento
Monolito modular
Una aplicación desplegable
Módulos con responsabilidades, datos encapsulados y APIs internas explícitas
Mantiene simplicidad operativa y mejora la modularidad
Microservicios
Varios servicios desplegables
Cada servicio controla sus datos; se comunican por red
Despliegue y escalado independientes; más complejidad operativa
Monolito no significa necesariamente código desordenado: puede tener módulos bien diseñados. Es una opción adecuada para aplicaciones pequeñas y poco complejas, como un CRUD sencillo.
Monolito modular: los módulos colaboran dentro de un mismo proceso mediante APIs explícitas. Las fronteras limitan dependencias y acceso a datos internos.
Microservicio: servicio autónomo que se despliega por separado y es responsable de sus datos. No basta con separar una aplicación en varios proyectos.
No hay que confundir cómo se organiza el código (por capas, hexagonal, cebolla) con cómo se despliega (monolito, monolito modular, microservicios). Un monolito puede aplicar principios de arquitectura limpia.
Microservicios no son simplemente módulos separados en carpetas o proyectos: cada servicio debe poder desplegarse y evolucionar de forma autónoma, y la comunicación pasa por la red.
3.2 Tipos de comunicación
La comunicación entre componentes puede ser síncrona o asíncrona. La forma concreta depende de si los componentes están dentro del mismo proceso o son servicios distribuidos.
Tipo
Funcionamiento
Características
Síncrona
El emisor hace una llamada y espera una respuesta
Directa; el emisor depende de la disponibilidad y el tiempo de respuesta del receptor
Asíncrona
El emisor publica un evento o mensaje y no espera a que se complete su procesamiento
Desacopla temporalmente; puede haber retrasos, reintentos y entregas duplicadas
Comunicación entre módulos
Los módulos de una misma aplicación pueden comunicarse de forma síncrona, mediante llamadas directas a las APIs públicas de otros módulos.
También pueden comunicarse de forma asíncrona, publicando eventos que otros módulos consumen.
Los eventos internos no implican comunicación de red ni crean por sí solos aislamiento o una transacción independiente.
Comunicación entre microservicios
Los microservicios se comunican a través de la red.
La comunicación síncrona suele realizarse mediante peticiones HTTP y respuestas.
La comunicación asíncrona se realiza publicando mensajes o eventos en un intermediario (broker), como RabbitMQ o Kafka; los consumidores los procesan posteriormente.
En cualquiera de los estilos distribuidos, hay que contemplar fallos de red. Con mensajería, los consumidores deben ser idempotentes para tolerar entregas duplicadas.
La mensajería desacopla al emisor del momento en que se procesa el evento, pero no garantiza que cada mensaje se procese exactamente una vez. Diseña consumidores idempotentes.
Durante el curso estudiaremos las capas y sus componentes en temas separados. Como referencia, seguiremos una arquitectura por capas desplegada como monolito modular.