Skip to content

Instantly share code, notes, and snippets.

@vasmarfas
Created October 31, 2025 12:01
Show Gist options
  • Select an option

  • Save vasmarfas/8b610a3a73220a370c81e445127b8900 to your computer and use it in GitHub Desktop.

Select an option

Save vasmarfas/8b610a3a73220a370c81e445127b8900 to your computer and use it in GitHub Desktop.

Hotel Booking System - Микросервисная архитектура

Распределённая система бронирования отелей на базе Spring Boot с использованием микросервисной архитектуры.

Архитектура системы

Система состоит из 4 модулей:

┌─────────────┐
│   Client    │
└──────┬──────┘
       │
       ▼
┌─────────────────┐      ┌──────────────┐
│  API Gateway    │◄────►│    Eureka    │
│   (Port 8084)   │      │  (Port 8761) │
└────────┬────────┘      └──────────────┘
         │
    ┌────┴────┐
    │         │
    ▼         ▼
┌─────────┐ ┌─────────┐
│ Booking │ │  Hotel  │
│ Service │◄┤ Service │
│ (8083)  │ │ (8082)  │
└─────────┘ └─────────┘

Компоненты

  1. Eureka Server (8761) - Service Discovery
  2. API Gateway (8084) - Точка входа, маршрутизация
  3. Hotel Service (8082) - Управление отелями и номерами
  4. Booking Service (8083) - Бронирования, аутентификация пользователей

Технологический стек

  • Java 17
  • Spring Boot 3.3.5
  • Spring Cloud 2023.0.3
  • Spring Security + JWT
  • Spring Data JPA + H2 (in-memory)
  • Spring Cloud Gateway
  • Spring Cloud Netflix Eureka
  • OpenFeign для межсервисного взаимодействия
  • Spring Retry для устойчивости
  • Lombok
  • JUnit 5 + MockMvc

Запуск проекта

Последовательность запуска

Запускать модули строго в указанном порядке:

# 1. Eureka Server
cd eureka-server
../gradlew bootRun

# 2. Hotel Service (в новом терминале)
cd hotel-service
../gradlew bootRun

# 3. Booking Service (в новом терминале)
cd booking-service
../gradlew bootRun

# 4. API Gateway (в новом терминале)
cd api-gateway
../gradlew bootRun

Подождите 10-20 секунд после запуска каждого сервиса для регистрации в Eureka.

Проверка статуса

Использование API

1. Регистрация пользователя

curl -X POST http://localhost:8084/user/register \
  -H "Content-Type: application/json" \
  -d '{"username": "testuser", "password": "test123"}'

Ответ:

{
  "token": "eyJhbGc...",
  "username": "testuser",
  "role": "USER"
}

2. Авторизация

curl -X POST http://localhost:8084/user/auth \
  -H "Content-Type: application/json" \
  -d '{"username": "user", "password": "user"}'

Предзаполненные пользователи:

  • admin / admin (роль: ADMIN)
  • user / user (роль: USER)

3. Получить список отелей

curl -X GET http://localhost:8084/api/hotels \
  -H "Authorization: Bearer YOUR_TOKEN"

4. Получить рекомендованные номера (с сортировкой по загруженности)

curl -X GET http://localhost:8084/api/rooms/recommend \
  -H "Authorization: Bearer YOUR_TOKEN"

5. Создать бронирование (с автоподбором номера)

curl -X POST http://localhost:8084/booking \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startDate": "2025-11-01",
    "endDate": "2025-11-05",
    "autoSelect": true
  }'

6. Создать бронирование (с указанием номера)

curl -X POST http://localhost:8084/booking \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "roomId": 1,
    "startDate": "2025-11-01",
    "endDate": "2025-11-05",
    "autoSelect": false
  }'

7. Получить свои бронирования

curl -X GET http://localhost:8084/bookings \
  -H "Authorization: Bearer YOUR_TOKEN"

8. Отменить бронирование

curl -X DELETE http://localhost:8084/booking/1 \
  -H "Authorization: Bearer YOUR_TOKEN"

9. Создать отель (только ADMIN)

curl -X POST http://localhost:8084/api/hotels \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "New Hotel",
    "address": "123 Street"
  }'

10. Создать номер (только ADMIN)

curl -X POST http://localhost:8084/api/rooms \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "hotelId": 1,
    "number": "401"
  }'

Ключевые особенности реализации

1. Алгоритм планирования занятости номеров

Реализован алгоритм равномерного распределения нагрузки на номера:

  • Каждый номер имеет счётчик timesBooked
  • При запросе рекомендаций возвращаются номера, отсортированные по возрастанию timesBooked, затем по id
  • При создании бронирования с autoSelect: true автоматически выбирается наименее загруженный номер
  • Это предотвращает «простой» номеров и обеспечивает равномерную загрузку

2. Сага с компенсацией (Booking ↔ Hotel)

Реализован двухшаговый процесс бронирования:

  1. PENDING: Booking Service создаёт запись бронирования со статусом PENDING
  2. Подтверждение: Вызов Hotel Service для подтверждения доступности (POST /api/rooms/{id}/confirm-availability)
  3. CONFIRMED: При успехе статус меняется на CONFIRMED
  4. Компенсация: При ошибке:
    • Статус меняется на CANCELLED
    • Вызывается POST /api/rooms/{id}/release для отката изменений в Hotel Service

3. Устойчивость и Retry

  • Используется @Retryable с экспоненциальным backoff
  • Максимум 3 попытки с увеличивающимся интервалом (500ms × 2)
  • Timeout на Feign клиентах: 2 секунды
  • При исчерпании попыток выполняется компенсация

4. Идемпотентность

  • Каждый запрос бронирования получает уникальный requestId
  • Hotel Service хранит обработанные requestId в памяти
  • Повторные запросы с тем же requestId игнорируются
  • Это предотвращает дубликаты при повторных попытках

5. Трассировка запросов

  • API Gateway добавляет заголовок X-Correlation-Id ко всем запросам
  • В логах используется bookingId для отслеживания саги
  • Все ключевые шаги логируются: создание, подтверждение, компенсация

6. Безопасность

  • JWT токены со сроком действия 1 час
  • Роли: USER (личные операции) и ADMIN (управление системой)
  • Каждый сервис самостоятельно валидирует JWT (Resource Server pattern)
  • @PreAuthorize на методах контроллеров
  • Корректные HTTP статусы: 401 (Unauthorized), 403 (Forbidden)

Тестирование

Запуск тестов:

# Все тесты
./gradlew test

# Только один модуль
./gradlew :hotel-service:test
./gradlew :booking-service:test

Реализованы тесты:

  • Позитивные сценарии: регистрация, аутентификация, создание бронирования
  • Негативные сценарии: неверные credentials, невалидные даты, доступ без прав
  • Интеграционные: проверка саги, компенсации, retry
  • Security: проверка ролей ADMIN/USER

Предзаполненные данные

Hotel Service

  • 3 отеля: Grand Hotel, City Inn, Resort Paradise
  • 15 номеров (по 5 на отель)

Booking Service

  • admin / admin (ADMIN)
  • user / user (USER)

Архитектурные решения (ADR)

Почему сага вместо распределённых транзакций?

Распределённые транзакции (2PC) сложны, требуют координатора и блокируют ресурсы. Сага с компенсацией проще, более отказоустойчива и лучше подходит для микросервисов.

Почему JWT проверяется в каждом сервисе?

Это паттерн Resource Server - каждый сервис независим и может работать без Gateway. Gateway остаётся простым маршрутизатором.

Почему H2 in-memory?

Для демонстрации и тестирования. В production заменить на PostgreSQL/MySQL без изменения кода (только application.yml).

Почему Feign вместо RestTemplate?

Feign декларативный, интегрирован с Spring Cloud (service discovery, load balancing), проще в использовании.

Структура кода

Каждый сервис следует layered architecture:

com.booking.{service}
├── entity       # JPA сущности
├── repository   # Spring Data репозитории
├── service      # Бизнес-логика
├── controller   # REST endpoints
├── dto          # Data Transfer Objects
├── security     # JWT фильтры, конфигурация
├── client       # Feign клиенты (только Booking)
├── config       # Конфигурация, инициализация данных
└── exception    # Обработка ошибок
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment