01 - Arquitecturas de software
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.
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.
2.2 Arquitectura hexagonal (Ports and Adapters)
Separa el núcleo de la aplicación de los sistemas externos mediante puertos y adaptadores.
- Puerto: abstracción que describe cómo se usa el núcleo o qué necesita del exterior. Por ejemplo, una interfaz de repositorio.
- Adaptador: conecta un puerto con una tecnología concreta. Por ejemplo, un
controllerREST o una implementación JPA. - Dirección de dependencia: el adaptador implementa o invoca el puerto; el dominio no conoce el adaptador.
2.3 Comparativa de enfoques internos
| Enfoque | Idea principal | Observación |
|---|---|---|
| Capas tradicional | Agrupar por responsabilidad y delegar entre capas | 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.
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.