Проект представляет собой разработку REST API для системы бронирования отелей на базе Spring Boot с использованием микросервисной архитектуры.
Цель — создать распределённое приложение с несколькими сервисами, реализующими основные функции бронирования, управления гостиницами и маршрутизации запросов через API Gateway.
Система состоит из 5 модулей:
- shared - Общие DTO, exceptions, IdempotencyRecord, API константы
- eureka-server (порт 8761) - Service Registry для динамического обнаружения сервисов
- gateway (порт 8080) - API Gateway с JWT аутентификацией и трассировкой запросов
- hotel-service (порт 8082) - Управление отелями и номерами
- booking-service (порт 8083) - Управление бронированиями и пользователями
graph TB
%% External Layer
Client["`**Клиент**<br/> / Web Browser`"]
%% Gateway Layer
subgraph Gateway["`**API Gateway Layer**<br/>:8080`"]
GW["`**API Gateway**<br/>Spring Cloud Gateway`"]
CF1["`**CorrelationIdFilter**<br/>X-Correlation-Id`"]
CF2["`**JwtAuthenticationFilter**<br/>JWT Validation`"]
CF3["`**InternalTokenFilter**<br/>X-Internal-Token`"]
end
%% Service Discovery
subgraph Discovery["`**Service Discovery**<br/>:8761`"]
Eureka["`**Eureka Server**<br/>Service Registry`"]
end
%% Booking Service
subgraph Booking["`**Booking Service**<br/>:8083`"]
BS["`**BookingController**<br/>REST API`"]
UC["`**UserController**<br/>User Management`"]
BSS["`**BookingSagaService**<br/>Saga Orchestrator`"]
HSC["`**HotelServiceClient**<br/>WebClient + Resilience4j`"]
SCS["`**SagaCompensationService**<br/>Error Recovery`"]
BDB[("`**Booking Database**<br/>H2 In-Memory<br/>• bookings<br/>• users<br/>• idempotency_records`")]
end
%% Hotel Service
subgraph Hotel["`**Hotel Service**<br/>:8082`"]
HC["`**HotelController**<br/>Hotel Management`"]
RC["`**RoomController**<br/>Room Operations`"]
RS["`**RoomService**<br/>Business Logic`"]
HDB[("`**Hotel Database**<br/>H2 In-Memory<br/>• hotels<br/>• rooms<br/>• room_slots<br/>• idempotency_records`")]
end
%% Client to Gateway
Client -->|"`**HTTP Requests**<br/>REST API`"| GW
%% Gateway Filters Chain
GW --> CF1
CF1 --> CF2
CF2 --> CF3
%% Gateway to Services
CF3 -->|"`**Booking Operations**<br/>/v1/api/booking/**<br/>/v1/api/user/**`"| BS
CF3 -->|"`**Hotel Operations**<br/>/v1/api/hotels/**<br/>/v1/api/rooms/**`"| HC
%% Service Discovery
GW -.->|"`**Service Discovery**<br/>Load Balancing`"| Eureka
BS -.->|"`**Register**<br/>Health Checks`"| Eureka
HC -.->|"`**Register**<br/>Health Checks`"| Eureka
%% Booking Service Internal Flow
BS --> BSS
BSS --> HSC
BSS --> SCS
HSC -->|"`**Internal API**<br/>X-Internal-Token<br/>confirmAvailability<br/>confirmBooking<br/>releaseSlot`"| RC
SCS -->|"`**Compensation**<br/>Error Recovery`"| RC
%% Database Connections
BSS --> BDB
UC --> BDB
RS --> HDB
RC --> RS
%% Styling
classDef clientStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef gatewayStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef discoveryStyle fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef bookingStyle fill:#e8f5e9,stroke:#388e3c,stroke-width:2px,color:#000
classDef hotelStyle fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,color:#000
classDef dbStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000
class Client clientStyle
class GW,CF1,CF2,CF3 gatewayStyle
class Eureka discoveryStyle
class BS,UC,BSS,HSC,SCS bookingStyle
class HC,RC,RS hotelStyle
class BDB,HDB dbStyle
- Java 17
- Spring Boot 3.5.6
- Spring Cloud 2025.0.0 (Gateway, Eureka)
- Spring Security с JWT аутентификацией
- Spring Data JPA + H2 (in-memory)
- WebFlux (WebClient для межсервисного взаимодействия)
- Resilience4j (Retry, TimeLimiter)
- MapStruct (маппинг DTO)
- Lombok
- Swagger/OpenAPI (документация)
- JUnit 5 + Mockito (тестирование)
- Двухшаговое подтверждение бронирования: PENDING → CONFIRMED
- Автоматическая компенсация при сбое: PENDING → CANCELLED с освобождением слота
- Логирование всех шагов с correlationId для трассировки
- IdempotencyRecord для отслеживания повторных запросов
- Защита от дублирования бронирований
- DLQ (Dead Letter Queue) для анализа сбоев
- Сортировка номеров по
timesBooked(ASC) для равномерной загрузки - Автоподбор номера через параметр
autoSelect
- Pessimistic locking (
PESSIMISTIC_WRITE) для критических секций - Isolation level
SERIALIZABLEдля подтверждения доступности
- Retry с экспоненциальной задержкой (3 попытки, 500ms)
- CircuitBreaker для защиты от каскадных сбоев
- Graceful degradation при недоступности Hotel Service
- X-Correlation-Id генерируется в Gateway
- MDC для логирования correlationId во всех сервисах
- X-Internal-Token для межсервисного взаимодействия
- InternalServiceFilter проверяет токен и устанавливает Authentication для Internal Service
- WebClient автоматически добавляет токен в заголовки запросов
- Все Internal endpoints защищены:
/v1/api/rooms/recommend,/v1/api/rooms/*/confirm-availability,/v1/api/rooms/confirm-booking,/v1/api/rooms/release
- Все URL-ы управляются через
ApiConstantsв shared модуле - Единый префикс
/v1/apiдля всех эндпоинтов - Типобезопасность и консистентность URL-ов
- Легкость изменения версии API
Для production рекомендуется:
- Service Mesh с mTLS (Istio, Linkerd) для автоматической взаимной аутентификации
- Сетевая изоляция (Kubernetes NetworkPolicy, Docker private networks)
- Ротация токенов через секреты (K8s Secrets, AWS Secrets Manager)
Проблема 2PC: Требует блокировки ресурсов на всех сервисах, снижает доступность, плохо масштабируется
Решение: Использовать оркестрацию Saga с компенсацией вместо двухфазного коммита (2PC).
- Преимущества Saga:
- Асинхронность - сервисы не блокируются
- Высокая доступность - сбой одного сервиса не блокирует остальные
- Простота масштабирования
- Реализация:
- PENDING → confirm availability → confirm booking → CONFIRMED
- При сбое: компенсация через
releaseSlot()→ CANCELLED
Проблема: Internal endpoints (/rooms/recommend, /rooms/*/confirm-availability, /rooms/confirm-booking, /rooms/release) не должны быть доступны извне
Решение: Использовать простой токен в заголовке X-Internal-Token для защиты internal endpoints.
- Преимущества:
- Простота реализации для учебного проекта
- Не требует дополнительной инфраструктуры
- Недостатки:
- Токен может быть скомпрометирован
- Нет автоматической ротации
Для Production можно выбрать другие варианты, например:
- Service Mesh с mTLS (Istio, Linkerd) - автоматическая взаимная аутентификация
- Сетевая изоляция (Kubernetes NetworkPolicy) - доступ только внутри кластера
- Ротация токенов через Kubernetes Secrets или AWS Secrets Manager
Проблема: Race conditions при параллельном бронировании одного номера на одни даты
Решение: Использовать пессимистические блокировки (PESSIMISTIC_WRITE) и уровень изоляции SERIALIZABLE для критичных операций.
- Преимущества:
- Гарантия отсутствия phantom reads
- Защита от lost updates
- Корректная работа при высокой нагрузке
- Недостатки:
- Снижение производительности при высоком contention
- Возможны deadlocks (минимизированы через порядок блокировок)
Проблема: Retry механизмы могут привести к дублированию операций
Решение: Каждый запрос маркируется уникальным requestId (используется correlationId), повторные запросы отклоняются.
- Преимущества:
- Безопасные повторы запросов
- Защита от дубликатов при сетевых сбоях
- Реализация:
- Таблица
idempotency_recordsс уникальным индексом наrequest_id - Проверка перед выполнением операции
- Сохранение результата (SUCCESS/FAILED)
- Таблица
- JDK 17+
- Maven 3.6+
mvn clean installINTERNAL_SERVICE_TOKEN- токен для межсервисного взаимодействия (по умолчанию:dev-internal-secret-token-12345)export INTERNAL_SERVICE_TOKEN=your-secure-token-here
Важно: В production используйте криптостойкий токен (например, UUID или сгенерированный через openssl rand -hex 32).
cd eureka-server
mvn spring-boot:runПроверка: http://localhost:8761
cd hotel-service
mvn spring-boot:runПроверка: http://localhost:8082/swagger-ui.html
cd booking-service
mvn spring-boot:runПроверка: http://localhost:8083/swagger-ui.html
cd gateway
mvn spring-boot:runПроверка: http://localhost:8080
Для удобства тестирования всех эндпойнтов подготовлена Postman коллекция Booking_System.pc.json.
Импорт коллекции:
- Откройте Postman
- Нажмите Import в верхнем левом углу (или Ctrl + O)
- Выберите файл
Booking_System.pc.json - Коллекция появится в списке с готовыми запросами
Структура коллекции:
- Authentication - регистрация и авторизация (admin/user)
- User Management (ADMIN) - CRUD операции с пользователями
- Hotels (via Gateway) - управление отелями
- Rooms (via Gateway) - управление номерами
- Bookings (via Gateway) - создание и управление бронированиями
- Internal Saga Endpoints (INTERNAL) - внутренние API для Saga
- Direct Service Access - прямой доступ к Swagger, H2 Console, Eureka
Автоматизация:
- После успешной авторизации JWT токен автоматически сохраняется в переменные коллекции
- Переменные
admin_tokenиuser_tokenиспользуются для авторизации запросов - Настроены три базовых URL:
gateway_url,hotel_service_url,booking_service_url
Порядок использования:
- Запустите все сервисы (Eureka, Hotel, Booking, Gateway)
- Выполните запрос "Login as Admin" или "Login as User" из раздела Authentication
- Используйте остальные запросы с автоматически подставленным токеном
mvn testcd hotel-service
mvn test
cd booking-service
mvn testПросмотр зарегистрированных сервисов и их статусов.
- Hotel Service: http://localhost:8082/swagger-ui.html
- Booking Service: http://localhost:8083/swagger-ui.html
-
Hotel Service: http://localhost:8082/h2-console
- URL:
jdbc:h2:mem:hoteldb - Username:
sa - Password: (пусто)
- URL:
-
Booking Service: http://localhost:8083/h2-console
- URL:
jdbc:h2:mem:bookingdb - Username:
sa - Password: (пусто)
- URL:
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID отеля |
| name | VARCHAR(255) | Название отеля |
| address | VARCHAR(255) | Адрес отеля |
Индексы: Нет дополнительных (FK автоматически)
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID номера |
| hotel_id | BIGINT (FK) | ID отеля |
| number | VARCHAR(255) | Номер комнаты |
| available | BOOLEAN | Операционная доступность (не для бронирования) |
| times_booked | INTEGER | Счетчик бронирований (для алгоритма балансировки) |
Индексы: Нет дополнительных (FK автоматически)
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID слота |
| room_id | BIGINT (FK) | ID номера |
| start_date | DATE | Дата начала бронирования |
| end_date | DATE | Дата окончания бронирования |
| request_id | VARCHAR(255) | Корреляционный ID запроса (идемпотентность) |
| status | VARCHAR(50) | Статус слота: PENDING/CONFIRMED/CANCELLED |
| created_at | TIMESTAMP | Время создания записи |
Индексы:
- Уникальный:
(room_id, request_id)- защита от дубликатов idx_room_dates:(room_id, start_date, end_date)- оптимизация поиска конфликтов
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID записи |
| request_id | VARCHAR(255) | Уникальный ID запроса |
| payload | TEXT | JSON тело запроса |
| status | VARCHAR(50) | SUCCESS/FAILED/PENDING |
| created_at | TIMESTAMP | Время первой обработки |
| error_message | VARCHAR(255) | Сообщение об ошибке (если есть) |
Индексы:
- Уникальный:
request_id- быстрая проверка дубликатов
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID пользователя |
| username | VARCHAR(255) | Логин (уникальный) |
| password | VARCHAR(255) | BCrypt хэш пароля |
| role | VARCHAR(50) | Роль: USER или ADMIN |
Индексы:
- Уникальный:
username
| Поле | Тип | Описание |
|---|---|---|
| id | BIGINT (PK) | Уникальный ID бронирования |
| user_id | BIGINT (FK) | ID пользователя |
| room_id | BIGINT | ID номера (reference в Hotel Service) |
| start_date | DATE | Дата начала |
| end_date | DATE | Дата окончания |
| status | VARCHAR(50) | PENDING/CONFIRMED/CANCELLED |
| created_at | TIMESTAMP | Время создания |
| correlation_id | VARCHAR(255) | ID для трассировки Saga и идемпотентности |
Индексы:
- Уникальный:
correlation_id- защита от дублирования бронирований idx_user_id:user_id- быстрая выборка бронирований пользователя
Структура аналогична Hotel Service.
correlation_id (bookings):
- Используется как
requestIdдля идемпотентности - Прослеживается через все шаги Saga
- Логируется в MDC для трассировки запросов
request_id (room_slots):
- Связывает слот с конкретным запросом бронирования
- Позволяет повторно обрабатывать запрос без создания дубликатов
- Используется для компенсации при отмене
times_booked (rooms):
- Счетчик успешных бронирований номера
- Используется алгоритмом автоподбора для равномерной загрузки
- Инкрементируется при
confirmBooking(), декрементируется при отмене CONFIRMED бронирования
available (rooms):
- НЕ используется для проверки занятости по датам
- Отражает операционную доступность (ремонт, вывод из эксплуатации)
- Занятость определяется через
room_slotsпо датам
Все сервисы логируют события с correlationId для трассировки запросов:
[correlationId] Сообщение лога
Пример:
[a3f5b7c9-...] Starting booking saga for user: 2, correlationId: a3f5b7c9-...
[a3f5b7c9-...] Room availability confirmed for correlationId: a3f5b7c9-...
[a3f5b7c9-...] Booking confirmed for correlationId: a3f5b7c9-...
Все ошибки возвращаются в единообразном формате:
{
"status": 409,
"message": "Room is already booked for the selected dates",
"timestamp": "2025-10-20T12:34:56"
}Коды ответов:
- 200 - Успех
- 201 - Создан
- 204 - Нет содержимого (успешное удаление)
- 400 - Некорректный запрос
- 401 - Неавторизован
- 403 - Доступ запрещен
- 404 - Не найдено
- 409 - Конфликт (номер занят, дубликат запроса)
- 500 - Внутренняя ошибка сервера
- Регистрация пользователя → получение JWT
- Получение списка доступных номеров
- Создание бронирования (с указанием номера или автоподбором)
- Просмотр истории бронирований
- Отмена бронирования
- Попытка бронирования уже занятого номера → 409 Conflict
- Попытка доступа к чужому бронированию → 403 Forbidden
- Попытка создания отеля пользователем (не ADMIN) → 403 Forbidden
- Попытка бронирования без токена → 401 Unauthorized
- Сбой Hotel Service → компенсация, статус CANCELLED
- Повторная отправка одного и того же запроса → отклонение дубликата
- Повторное подтверждение доступности номера → игнорирование
- Создание нескольких бронирований с autoSelect
- Проверка, что номера выбираются с наименьшим timesBooked
- Проверка, что timesBooked инкрементируется после каждого бронирования