¡Hola y bienvenido! Este desarrollo se trata de un ejercicio propuesto en la comunidad de Skool DeHaroHub por Nacho De Haro (el creador de la comunidad) en este repositorio. En el propio enunciado pone que se debe realizar una PR en ese mismo repositorio para que toda la gente de la comunidad pueda revisarlo, aprender de él y aportar su granito de arena. Pero como la comunidad esta inactiva y actualmente cerrada porque el creador esta a otras cosas he optado por publicar mi desarrollo en este repositorio.
- Descripción
- Objetivo
- Características Funcionales
- Requerimientos Técnicos
- Arquitectura del Proyecto
- Tecnologías Utilizadas
- Instalación y Configuración
- Documentación de la API
- Pruebas
- Desafíos Adicionales y Mejoras Futuras
- Contribuciones
- Licencia
HotelManagementAPI es una API RESTful para la gestión de un hotel. Permite administrar clientes, habitaciones, reservas, pagos y administradores, incorpora autenticación y autorización con JWT, expone documentación OpenAPI/Swagger y mantiene la persistencia con JDBC directo sobre MariaDB, sin utilizar ORM.
El proyecto ha evolucionado hacia una arquitectura modular de estilo hexagonal: separa controladores, DTOs y seguridad en adaptadores; concentra la lógica de negocio en casos de uso; mantiene el dominio independiente mediante modelos y puertos de repositorio; y delega la infraestructura en implementaciones JDBC, configuración de base de datos, caché y mensajería.
Además de la API principal, incluye mejoras de escalabilidad como caché con Redis para consultas de disponibilidad de habitaciones y un flujo orientado a eventos con RabbitMQ. Cuando se crea una reserva, la API publica un evento BookingCreatedEvent y el módulo independiente notification-worker lo consume para enviar emails HTML de confirmación usando Spring Mail y Thymeleaf.
El objetivo de este proyecto es crear una API para la gestión de un hotel que permita:
- Manejar reservas, habitaciones, pagos, clientes, usuarios y administradores con control de acceso por roles.
- Implementar una API RESTful documentada con OpenAPI y organizada con principios de arquitectura hexagonal.
- Trabajar con persistencia JDBC directa para tener mayor control sobre las consultas SQL y la estructura de datos.
- Incorporar componentes de infraestructura habituales en sistemas reales, como Redis para caché y RabbitMQ para comunicación asíncrona.
- Practicar pruebas unitarias, de controlador, de persistencia y de integración con JUnit5, Mockito y Testcontainers.
- CRUD: Crear, leer, actualizar y eliminar clientes.
- Campos obligatorios: ID, Nombre, Apellidos, Correo electrónico, Número de teléfono.
- CRUD: Crear, leer, actualizar y eliminar habitaciones.
- Campos obligatorios: ID, Número de habitación, Tipo de habitación (simple, doble, suite), Precio por noche, Estado (disponible, ocupada, en mantenimiento).
- Consultas públicas: Listado de habitaciones disponibles y filtrado por tipo.
- Caché: La disponibilidad de habitaciones se cachea con Redis y se invalida cuando cambian reservas o estados de habitación.
- CRUD: Crear, leer, actualizar y cancelar reservas.
- Campos obligatorios: ID, ID del cliente, ID de la habitación, Fecha de inicio, Fecha de fin, Estado (pendiente, confirmada, cancelada).
- Registro de pagos: Asociados a una reserva.
- Campos obligatorios: ID, ID de la reserva, Monto, Fecha de pago, Método de pago (tarjeta, efectivo, transferencia).
- CRUD: Crear, leer, actualizar y eliminar administradores.
- Campos obligatorios: ID, Nombre, Correo electrónico, Contraseña (hasheada), Rol (admin, superadmin).
- Implementación de autenticación JWT para administradores y clientes.
- Separación de permisos por roles
CLIENT,ADMINySUPERADMIN. - Los endpoints públicos permiten registro/login y consulta básica de habitaciones.
- Los clientes pueden consultar su perfil, actualizarlo, cambiar credenciales y gestionar sus reservas dentro de sus permisos.
- Los administradores y superadministradores acceden a operaciones internas de gestión según su rol.
- Publicación de eventos de reserva mediante RabbitMQ.
- Worker independiente
notification-workerpara procesar eventos de reserva. - Envío de emails HTML de confirmación con Spring Mail y plantillas Thymeleaf.
- Reintentos con backoff mediante colas intermedias y Dead Letter Queue para mensajes fallidos.
- Lenguaje y Framework: Java 21 con Spring Boot 3.4.1.
- Base de Datos: MariaDB ejecutado en un contenedor Docker, con un script sql para crear y poblar las tablas de la base de datos.
- Persistencia: JDBC directo, sin ORM, para mantener control explícito sobre las consultas.
- Seguridad: Spring Security con JWT y autorización basada en roles.
- Caché: Redis con Spring Cache para optimizar consultas de disponibilidad de habitaciones.
- Mensajería: RabbitMQ con Spring AMQP para publicar y consumir eventos de reservas.
- Notificaciones: Microservicio
notification-workercon Spring Mail y Thymeleaf. - Contenedores: Docker para MariaDB, Adminer, Redis y RabbitMQ.
- Documentación: Swagger/OpenAPI para la documentación de la API.
- Pruebas: Pruebas unitarias, de controladores, de persistencia e integración con JUnit5, Mockito, Spring Security Test, Testcontainers y Postman para pruebas manuales de la API.
erDiagram
User {
INT id PK
VARCHAR email
VARCHAR password
ENUM role "CLIENT, ADMIN, SUPERADMIN"
}
Client {
INT id PK
INT user_id FK
VARCHAR first_name
VARCHAR last_name
VARCHAR phone
}
Administrator {
INT id PK
INT user_id FK
VARCHAR name
}
Room {
INT id PK
INT room_number
ENUM room_type "SINGLE, DOUBLE, SUITE"
DECIMAL price_per_night
ENUM status "AVAILABLE, OCCUPIED, MAINTENANCE"
}
Reservation {
INT id PK
INT client_id FK
INT room_id FK
DECIMAL total_price
DATE start_date
DATE end_date
ENUM status "PENDING, CONFIRMED, CANCELED"
}
Payment {
INT id PK
INT reservation_id FK
DECIMAL amount
DATE payment_date
ENUM payment_method "CARD, CASH, TRANSFER"
}
Client ||--o{ Reservation : has
Room ||--o{ Reservation : "is booked in"
Reservation ||--o{ Payment : has
User ||--|| Client : "is a"
User ||--|| Administrator : "is a"
El proyecto está organizado siguiendo una arquitectura limpia:
-
Adaptadores:
- Controladores REST para recibir peticiones y enviar respuestas.
- DTOs para el mapeo de datos entre la API y la lógica de negocio.
- Adaptador de seguridad (JWT, filtros, etc.).
-
Aplicación:
- Casos de Uso que orquestan la lógica de negocio.
-
Dominio:
- Entidades de dominio (Modelos) y lógica de negocio.
- Interfaces de repositorios (Puertos).
-
Infraestructura:
- Implementaciones de repositorios (con JDBC).
- Configuración de acceso a la base de datos (MariaDB en Docker).
graph TD;
subgraph CLIENTE
Client["💻 Cliente (Front-end)"]
end
subgraph ADAPTADORES
Controller["🌐 @RestController Controladores Web"]
DTOs["🔌 DTOs
(Request/Response)"]
Security["🔐 Filtros y JWT
(Adaptador de Seguridad)"]
end
subgraph APLICACIÓN
UseCase["⚙️ Casos de Uso
(Lógica de Negocio)"]
end
subgraph DOMINIO
Entities["🗃️ Entidades de Dominio (Modelos)"]
Ports["📁 Interfaces de Repositorios (Puertos)"]
end
subgraph INFRAESTRUCTURA
RepoImpl["📂 @Repository Implementación de Repositorios con JDBC"]
DB["🗄️🐋 Base de Datos (MariaDB)"]
end
Client -- "HTTP Request" --> Controller
Controller -- "DTO Mapping" --> UseCase
UseCase -- "Invoca reglas de negocio" --> Entities
UseCase -- "Solicita persistencia" --> Ports
Ports -- "Implementado por" --> RepoImpl
RepoImpl -- "Acceso a datos" --> DB
Controller -- "HTTP Response" --> Client
%% Opcional: Integración de seguridad
Controller -- "Autenticación/Autorización" --> Security
graph TD;
subgraph CLIENTE
Cliente["💻 Cliente (Front-end)"]
end
subgraph FILTROS
JwtFilter["🔍 JwtAuthenticationFilter{}🔸 Verifica si el JWT es null"]
end
subgraph AUTENTICACION
AuthController["🔐 AuthenticationController{}"]
AuthService["⚙️ AuthenticationService{}"]
JwtService["🛠️ JwtService{}
🔸Genera JWT Token"]
end
subgraph REPOSITORIO
UserRepo["📂UserRepository{} 🔸Guarda/Obtiene UserDetail"]
User["🧑💼 User{}
🔸Implementa UserDetails"]
end
subgraph CONFIGURACION
Config["⚙️ ApplicationConfig 🔸Authentication Manager 🔸Providers 🔸PasswordEncoders"]
end
subgraph BASE DE DATOS
DB[("🗄️🐋 Base de Datos (MariaDB)")]
end
%% Flujo del proceso de autenticación
Cliente -- "HTTP Request" --> JwtFilter
JwtFilter --> AuthController
AuthController --> AuthService
AuthService --> UserRepo
UserRepo --> User
User --> DB
AuthService --> JwtService
JwtService --> AuthController
AuthController -- "HTTP Response (JWT Token)" --> Cliente
%% Conexiones de configuración
Config -.-> AuthService
graph TD;
subgraph CLIENTE
Cliente["💻 Cliente (Front-end)"]
end
subgraph FILTROS
JwtFilter["🔍 JwtAuthenticationFilter{}🔸 Verifica el JWT"]
end
subgraph SERVICIOS
JwtService["🛠️ JwtService{}
🔸Extrae el usuario del JWT
🔸Verifica el token"]
UserDetailsService["⚙️ UserDetailsService{} 🔸 loadUserByUsername()"]
end
subgraph REPOSITORIO
User["🧑💼 User{}
🔸Implementa UserDetails"]
DB[("🗄️🐋 Base de Datos")]
end
subgraph SecurityContext
Authentication["🔐Authentication
Principle | Credentials | Authorities"]
end
subgraph CONTROLADOR
Controller["⚡ Controller{}"]
end
%% Flujo del proceso de validación JWT
Cliente -- "HTTP Request (Token)" --> JwtFilter
JwtFilter --> JwtService
JwtService --> UserDetailsService
UserDetailsService --> User
User --> DB
JwtFilter --> Authentication
Authentication --> Controller
Controller -- "✅ HTTP Response (JSON)" --> Cliente
%% Manejo de errores (403)
JwtFilter -- "❌ HTTP 403: Token inválido" --> Cliente
JwtFilter -- "❌ HTTP 403: Falta token o usuario no existe" --> Cliente
Para transformar este monolito en un sistema preparado para alta carga, se realizaron dos mejoras estructurales:
- Caché con Redis: Optimización del endpoint de consulta de habitaciones disponibles mediante
@Cacheable("room-availability"). La caché tiene un TTL de 5 minutos y se invalida automáticamente al crear, modificar o cancelar una reserva, y también al cambiar el estado de una habitación. - Arquitectura Orientada a Eventos (EDA): Se desacopló el flujo de notificaciones mediante RabbitMQ. Al crear una reserva, la API publica un
BookingCreatedEventen el exchangehotel.exchangecon la routing keybooking.created. El microservicio independientenotification-workerconsume la colahotel.notificationsy envía un email de confirmación al huésped.
Cliente / Admin
|
v
hotel-api
|
| BookingCreatedEvent
v
RabbitMQ (hotel.exchange)
|
v
Cola hotel.notifications
|
v
notification-worker
|
v
Email de confirmación al huésped
El sistema de mensajería incluye reintentos automáticos con backoff exponencial usando colas intermedias:
hotel.notifications
|
| fallo
v
retry.1 (5s) -> retry.2 (25s) -> retry.3 (125s) -> hotel.notifications.dlq
Si el envío del email falla en los reintentos configurados, el mensaje termina en la Dead Letter Queue hotel.notifications.dlq para su revisión posterior.
El proyecto incluye el módulo independiente notification-worker, encargado de procesar las notificaciones de reservas:
- Escucha la cola
hotel.notifications. - Consume eventos
BookingCreatedEventserializados en JSON. - Envía emails HTML con Spring Mail.
- Usa una plantilla Thymeleaf en español (
booking-confirmation.html) para el email de confirmación de reserva.
- Java 21+
- Spring Boot
- Spring Security con JWT
- JDBC
- RabbitMQ
- Redis
- Spring AMQP
- Spring Cache
- Spring Mail
- Thymeleaf
- Docker
- MariaDB
- Adminer
- Redis
- RabbitMQ
- Swagger/OpenAPI
- Gradle
- JUnit5
- Mockito
- Testcontainers
- Postman
- JDK 21 o superior instalado.
- Docker y Docker Compose instalados.
- Git instalado.
El proyecto incluye un archivo docker-compose.yml para levantar la infraestructura necesaria de la aplicación:
- MariaDB: base de datos principal, disponible en el puerto
3306. - Adminer: panel web para gestionar la base de datos, disponible en
http://localhost:8081. - Redis: caché de disponibilidad de habitaciones, disponible en el puerto
6379. - RabbitMQ: broker de eventos, disponible por AMQP en el puerto
5672. - Panel de RabbitMQ: consola de administración disponible en
http://localhost:15672.
La API principal y el worker de notificaciones se ejecutan como aplicaciones Spring Boot desde Gradle. El docker-compose.yml actual levanta únicamente la infraestructura externa necesaria: MariaDB, Adminer, Redis y RabbitMQ.
Para iniciar los contenedores, ejecuta en la raíz del proyecto:
docker compose up -dCaution
Antes de ejecutar el código fuente de la API, el contenedor Docker con la base de datos MariaDB debe estar corriendo.
-
Clona el repositorio:
git clone https://github.com/asobrados03/HotelManagementAPI.git cd HotelManagementAPI -
Compila y ejecuta la aplicación:
./gradlew build ./gradlew bootRun
-
La API estará disponible en
http://localhost:8080. -
Para ejecutar el worker de notificaciones:
./gradlew :notification-worker:bootRun
La documentación interactiva se genera automáticamente con Swagger. Una vez iniciada la aplicación, puedes acceder a ella en:
http://localhost:8080/swagger-ui.htmlohttp://localhost:8080/swagger-ui/index.html
Se han implementado pruebas unitarias y de integración para asegurar el correcto funcionamiento de la API. Para ejecutarlas:
./gradlew testImportant
Para las pruebas de integración debe estar corriendo Docker en la máquina.
- Optimización de Consultas: Mejorar el rendimiento en operaciones complejas sobre reservas y habitaciones.
- Gestión de Estados: Refinar la lógica de transición de estados en reservas.
- Manejo de Concurrencia: Evitar sobre-reservas mediante bloqueos o estrategias de concurrencia.
- Despliegue de Servicios: Incorporar imágenes Docker para la API y el
notification-workerdentro del despliegue completo. - Observabilidad: Ampliar métricas, trazas y logs para monitorizar reservas, caché y procesamiento de eventos.
- Seguridad: Mejorar la protección contra ataques (SQL Injection, XSS, etc.) y optimizar el manejo de autenticación y autorización.
¡Las contribuciones son bienvenidas! Si deseas colaborar en el proyecto, sigue estos pasos:
- Realiza un fork del repositorio.
- Crea una rama para tu funcionalidad:
git checkout -b feature/nueva-funcionalidad. - Realiza tus cambios y haz commit.
- Envía un pull request describiendo los cambios realizados.
Este proyecto se distribuye bajo la Licencia MIT.
