====== 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**.