====== 01 - Arquitecturas de software ====== Una aplicación puede clasificarse según distintos criterios. Por ejemplo, puede desplegarse como **monolito modular** y organizarse internamente por **capas** con **inversión de dependencias**. ===== 1. Principios de arquitectura limpia ===== La **arquitectura limpia** protege las reglas del negocio de los detalles técnicos y externos: ^ Principio ^ Idea clave ^ | **Independencia de frameworks** | El framework es una herramienta, no el centro de la aplicación | | **Testeable** | Las reglas se prueban sin levantar interfaz, base de datos ni framework | | **Independencia de la interfaz** | La misma lógica puede exponerse mediante interfaces distintas | | **Independencia de la base de datos** | Se puede cambiar la persistencia sin reescribir las reglas | | **Independencia de agentes externos** | El negocio no depende de servicios externos concretos | | **Regla de dependencia** | El código depende hacia las políticas de negocio, no hacia los detalles | | **Inversión de dependencias (DIP)** | El núcleo define las abstracciones que necesita; los detalles externos las implementan | @startuml allowmixing package "controller" #A5D6A7 { class Controller } package "domain" #EF5350 { interface Service class ServiceImpl class Model interface Repository } package "persistence" #90CAF9 { class RepositoryImpl } database "Base de datos" as db Controller --> Service : invoca ServiceImpl ..|> Service ServiceImpl --> Model : aplica reglas ServiceImpl --> Repository : utiliza RepositoryImpl ..|> Repository RepositoryImpl --> db : accede @enduml La **arquitectura limpia** es un concepto general: protege las reglas de negocio y dirige las dependencias hacia el núcleo. La **arquitectura hexagonal**, la **arquitectura de cebolla** y la arquitectura por capas con **inversión de dependencias** son enfoques distintos que aplican estas ideas. ===== 2. Organización interna ===== ==== 2.1 Arquitectura por capas ==== La **arquitectura por capas** agrupa componentes por responsabilidad. En esta arquitectura de referencia: ^ Capa ^ Componentes y responsabilidad ^ | ''controller'' | **Controladores** y **DTO** de entrada y salida; comunica la aplicación con clientes externos | | ''domain'' | Interfaces e implementaciones de **servicios**, **modelos** e interfaces de **repositorio**; contiene las reglas del negocio | | ''persistence'' | Implementaciones de repositorio y **DAO**; accede a la base de datos | @startuml allowmixing package "controller" #A5D6A7 { class Controller } package "domain" #EF5350 { interface Service class ServiceImpl class Model interface Repository } package "persistence" #90CAF9 { class RepositoryImpl interface DAO class DAOImpl } database "Base de datos" as db Controller --> Service : invoca ServiceImpl ..|> Service ServiceImpl --> Model : aplica reglas ServiceImpl --> Repository : utiliza RepositoryImpl ..|> Repository RepositoryImpl --> DAO : utiliza DAOImpl ..|> DAO DAOImpl --> db : accede @enduml ''Service'' y ''Repository'' son interfaces; ''ServiceImpl'' y ''RepositoryImpl'' las implementan. **DAO** también se separa en interfaz e implementación, ambas dentro de ''persistence''. ''domain'' define la interfaz ''Repository'' que necesita; ''persistence'' la implementa. Esta **inversión de dependencias** evita que el dominio dependa de la tecnología de persistencia y aplica un principio de arquitectura limpia. Las capas son una división lógica, no una estructura rígida. Se pueden añadir, dividir o reorganizar según las necesidades de la aplicación. ==== 2.2 Arquitectura hexagonal (Ports and Adapters) ==== La **arquitectura hexagonal** separa el núcleo de los sistemas externos mediante **puertos** (interfaces que definen cómo se usa o qué necesita el núcleo) y **adaptadores** (componentes que conectan esas interfaces con tecnologías concretas). @startuml allowmixing package "Adaptadores de entrada" #A5D6A7 { class RestController } package "Núcleo de la aplicación" #EF5350 { interface InputPort class BusinessService class Model interface RepositoryPort interface NotificationPort } package "Adaptador de persistencia" #90CAF9 { class RepositoryAdapter } package "Adaptador de servicio externo" #90CAF9 { class EmailAdapter } database "Base de datos" as db cloud "Servicio de correo" as mail RestController --> InputPort : invoca BusinessService ..|> InputPort BusinessService --> Model : aplica reglas BusinessService --> RepositoryPort : utiliza RepositoryAdapter ..|> RepositoryPort : implementa RepositoryAdapter --> db BusinessService --> NotificationPort : utiliza EmailAdapter ..|> NotificationPort : implementa EmailAdapter --> mail @enduml ==== 2.3 Arquitectura de cebolla ==== La **arquitectura de cebolla** organiza el sistema en anillos alrededor del dominio. Las dependencias apuntan desde los anillos externos hacia el núcleo. @startuml allowmixing package "Infraestructura (anillo exterior)" #90CAF9 { class RepositoryImpl package "Aplicación (anillo intermedio)" #A5D6A7 { class Service package "Dominio (núcleo)" #EF5350 { interface Repository class Model } } } Service --> Repository : usa Service --> Model : aplica reglas RepositoryImpl ..|> Repository : implementa @enduml ^ Enfoque ^ Idea principal ^ Observación ^ | **Capas** | Agrupa por responsabilidad y delega entre capas | Evitar que el dominio dependa directamente de tecnologías | | **Hexagonal** | Aísla el núcleo mediante puertos y adaptadores | Facilita sustituir tecnologías y probar el núcleo | | **Cebolla** | Organiza el sistema en anillos alrededor del dominio | Las dependencias apuntan hacia el centro | ===== 3. Arquitectura de despliegue ===== La **arquitectura de despliegue** describe cómo se empaqueta, ejecuta y despliega una aplicación; es un criterio distinto de su organización interna. ==== 3.1 Monolito, monolito modular y microservicios ==== ^ Estilo ^ Despliegue ^ Límites y datos ^ Coste o ventaja ^ | **Monolito** | Una aplicación desplegable | Los componentes pueden compartir proceso y almacenamiento | Operación sencilla; puede crecer con alto acoplamiento | | **Monolito modular** | Una aplicación desplegable | Módulos con límites y APIs internas explícitas | Modularidad sin multiplicar despliegues | | **Microservicios** | Varios servicios desplegables | Cada servicio controla sus datos y se comunica por red | Despliegue independiente; más complejidad operativa | Un **monolito** no implica código desordenado y puede ser adecuado para una aplicación pequeña, como un CRUD. En un **monolito modular**, los módulos colaboran dentro del mismo proceso mediante sus **APIs públicas**. @startuml allowmixing node "Monolito: un despliegue" { component "Aplicación\n(módulos juntos)" as monolith database "Base de datos" as monodb monolith --> monodb } node "Monolito modular: un despliegue" { package "Aplicación" { component "Módulo A\n(API pública)" as moduleA component "Módulo B\n(API pública)" as moduleB } database "Base de datos compartida\n(datos propiedad de cada módulo)" as modulardb moduleA --> modulardb : solo sus datos moduleB --> modulardb : solo sus datos moduleB --> moduleA : API interna } node "Microservicios: varios despliegues" { component "Servicio A" as serviceA component "Servicio B" as serviceB component "Servicio C" as serviceC database "BD A" as dbA database "BD B" as dbB database "BD C" as dbC serviceA --> dbA serviceB --> dbB serviceC --> dbC serviceC --> serviceB : HTTP / mensaje } @enduml No hay que confundir **cómo se organiza el código** (por **capas**, **hexagonal** o **cebolla**) con **cómo se despliega** (**monolito**, **monolito modular** o **microservicios**). Un monolito puede aplicar principios de **arquitectura limpia**. Los **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 ==== ^ Tipo ^ Funcionamiento ^ Característica ^ | **Síncrona** | El emisor hace una llamada y espera una respuesta | Depende de la disponibilidad y el tiempo de respuesta del receptor | | **Asíncrona** | El emisor publica un mensaje o evento para que se procese después | Desacopla temporalmente; puede haber retrasos o entregas duplicadas | Los módulos de una aplicación pueden comunicarse mediante llamadas a **APIs internas** o eventos. Los microservicios se comunican por red, mediante **HTTP** o un **intermediario de mensajes** como **RabbitMQ** o **Kafka**. Los eventos entre módulos no implican comunicación de red ni crean por sí solos aislamiento o una transacción independiente. La comunicación distribuida debe contemplar fallos de red. @startuml actor Cliente participant "Servicio A" as ServiceA participant "Servicio B" as ServiceB queue "Broker de mensajes" as Broker participant "Consumidor" as Consumer group Comunicación síncrona entre módulos (mismo proceso) ServiceA -> ServiceB : API pública (llamada directa) ServiceB --> ServiceA : resultado end group Comunicación síncrona entre microservicios (HTTP) Cliente -> ServiceA : solicitud ServiceA -> ServiceB : petición HTTP ServiceB --> ServiceA : respuesta ServiceA --> Cliente : resultado end group Comunicación asíncrona mediante eventos ServiceA -> Broker : publicar evento Broker -> Consumer : entregar evento Consumer --> Broker : confirmar procesamiento end @enduml 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. Los consumidores deben tolerar entregas duplicadas. Durante el curso se estudiarán las capas y sus componentes en temas separados. Como referencia, se seguirá una **arquitectura por capas** desplegada como **monolito modular**.