Найти в Дзене
Записки о Java

Реализация микросервиса продажи билетов в кинотеатр на Java (заготовка)

Цель: Создать простой, но надёжный микросервис для онлайн-бронирования мест в кинотеатре, устойчивый к одновременным запросам от множества пользователей. Название Жанр Длительность Интерстеллар Sci-Fi 169 мин Паразиты Драма 132 мин Матрица Экшен 136 мин Остров собак Анимация 101 мин Дюна Sci-Fi 155 мин Фильмы идут ежедневно весь месяц. Для простоты и обучения — единый Spring Boot микросервис (не распределённая система), но спроектированный так, чтобы его можно было легко разделить позже. [HTML Frontend] ↓ (HTTP POST/GET) [Spring Boot App] ↓ [PostgreSQL DB] Film { id: Long title: String duration: Integer // минуты } Showtime { id: Long filmId: Long dateTime: LocalDateTime // например, 2026-02-01T10:00 } Seat { id: Long row: Integer // 1–10 number: Integer // 1–10 } Booking { id: UUID showtimeId: Long seatId: Long bookedAt: LocalDateTime status: BookingStatus // CONFIRMED, EXPIRED } Важно: Seat — не привязан к залу, потому что у нас один зал. При масштабировании —
Оглавление
Рисунок: продажа билетов в кинотеатр
Рисунок: продажа билетов в кинотеатр

От идеи до production-ready решения с учётом конкуренции, блокировок и отказоустойчивости

Цель: Создать простой, но надёжный микросервис для онлайн-бронирования мест в кинотеатре, устойчивый к одновременным запросам от множества пользователей.

План статьи

  1. Постановка задачи
  2. System Design: архитектура и компоненты
  3. Аналитика: модели данных, диаграммы, инструменты
  4. Реализация (бэкенд + фронтенд)
  5. Проблемы и их решение: конкурентность, дублирование, отказоустойчивость
  6. Заключение и возможные улучшения

1. Постановка задачи

Требования:

  • Кинотеатр показывает фильмы с 10:00 до 00:00 по МСК.
  • Сеансы начинаются каждые 2 часа: 10:00, 12:00, 14:00, ..., 22:00 → 7 сеансов в день.
  • В зале 10 рядов × 10 мест = 100 мест.
  • Пользователь: выбирает фильм,
    выбирает дату и сеанс,
    выбирает конкретное место (ряд + номер),
    подтверждает бронирование.
  • Билет нельзя продать дважды.
  • Фронтенд — простой HTML + формы, без Thymeleaf, React и т.п.
  • Бэкенд — Spring Boot на Java 11.
  • Данные хранятся в PostgreSQL.

Пример списка фильмов (на 1 месяц):

Название Жанр Длительность

Интерстеллар Sci-Fi 169 мин

Паразиты Драма 132 мин

Матрица Экшен 136 мин

Остров собак Анимация 101 мин

Дюна Sci-Fi 155 мин

Фильмы идут ежедневно весь месяц.

2. System Design

Архитектура: Monolith vs Microservice?

Для простоты и обучения — единый Spring Boot микросервис (не распределённая система), но спроектированный так, чтобы его можно было легко разделить позже.

Компоненты:

[HTML Frontend]

↓ (HTTP POST/GET)

[Spring Boot App]

[PostgreSQL DB]

Endpoints:

  • GET /movies — список фильмов
  • GET /showtimes?date=2026-02-01&movieId=1 — сеансы на дату
  • GET /seats?showtimeId=5 — свободные/занятые места
  • POST /book — забронировать место

3. Аналитика: модели данных и диаграммы

Сущности:

Film {

id: Long

title: String

duration: Integer // минуты

}

Showtime {

id: Long

filmId: Long

dateTime: LocalDateTime // например, 2026-02-01T10:00

}

Seat {

id: Long

row: Integer // 1–10

number: Integer // 1–10

}

Booking {

id: UUID

showtimeId: Long

seatId: Long

bookedAt: LocalDateTime

status: BookingStatus // CONFIRMED, EXPIRED

}

Важно: Seat — не привязан к залу, потому что у нас один зал. При масштабировании — добавим hallId.

Диаграмма связей (ERD)

Можно нарисовать в:

Пример ERD (текстово):

Film ────< Showtime >─── Booking ─── Seat

  • Один фильм → много сеансов
  • Один сеанс → много бронирований
  • Одно место → может быть забронировано только один раз на сеанс

4. Реализация

Технологии:

  • Java 11
  • Spring Boot 2.7.x (поддержка Java 11)
  • Spring Web, Spring Data JPA
  • PostgreSQL
  • HikariCP (пул соединений)
  • Lombok (для геттеров/сеттеров)

4.1. Entity-классы

Рисунок: entity Film
Рисунок: entity Film
Рисунок: entity Showtime
Рисунок: entity Showtime
Рисунок: entity Seat
Рисунок: entity Seat
Рисунок: entity Booking
Рисунок: entity Booking

4.2. Репозитории

Рисунок: репозиторий BookingRepository
Рисунок: репозиторий BookingRepository

4.3. Контроллер бронирования

Рисунок: контроллер бронирования
Рисунок: контроллер бронирования

4.4. Простой HTML-фронтенд (static/indexcinema.html)

Рисунок:  статический html для бронирования билета
Рисунок: статический html для бронирования билета

5. Проблемы и их решение

❗ Проблема 1: Гонка за место (Race Condition)

Сценарий:
Два пользователя одновременно проверяют existsBy... → оба получают false → оба сохраняют бронь →
одно место продано дважды.

✅ Решение: Оптимистическая или пессимистическая блокировка

Вариант: UNIQUE-ограничение в БД (самый надёжный!)

Добавьте в таблицу bookings уникальный индекс:

ALTER TABLE bookings

ADD CONSTRAINT uk_showtime_seat UNIQUE (showtime_id, seat_id);

Теперь, если два потока попытаются вставить одну и ту же пару — второй получит DataIntegrityViolationException.

Обрабатываем в контроллере:

Рисунок: обработка в контролллере constraint
Рисунок: обработка в контролллере constraint

Проблема 2: Бронь без оплаты → места "зависают"

Пользователь забронировал, но не оплатил → место недоступно 2 часа.

✅ Решение: временная бронь + фоновая очистка

  • Добавьте поле expiresAt в Booking.
  • При бронировании: expiresAt = now() + 10 минут.
  • Запускайте @Scheduled задачу каждые 5 минут:

@Scheduled(fixedRate = 300_000) // 5 минут

public void cleanupExpiredBookings() {

bookingRepo.deleteByExpiresAtBefore(LocalDateTime.now());

}

Это требует поля expiresAt и cron-очистки, но предотвращает "заморозку" мест.

6. Заключение и возможные улучшения

Вы создали минимальный, но надёжный микросервис для продажи билетов, который:

  • Предотвращает двойную продажу через UNIQUE-ограничение,
  • Имеет чистую архитектуру,
  • Готов к расширению (залы, оплата, email-уведомления).

Что можно добавить дальше:

  • REST API для мобильного приложения
  • WebSocket для обновления карты мест в реальном времени
  • Интеграция с платежной системой
  • Кэширование списка сеансов (Redis)
  • Docker + Docker Compose для развёртывания

Заключение

Пример, рассмотренный в статье, можно найти по адресу:
https://github.com/ShkrylAndrei/blog_yandex/tree/main/src/main/java/info/examples/cinema