Əsas məzmuna keçin

Kino Bilet Rezervasiya Sistem Dizaynı

Problem Təsviri

İstifadəçilərə filmləri araşdırmaq, seans vaxtlarını seçmək, yerləri rezerv etmək və kino biletləri üçün ödəniş etmək imkanı verən kino bilet rezervasiya sistemi dizayn edin.

Tələblər

  1. Funksional Tələblər:

    • Filmlər və seans vaxtlarının araşdırılması
    • Müəyyən seans üçün yer mövcudluğunun görüntülənməsi
    • Film üçün yerlərin rezerv edilməsi
    • Rezervasyonlar üçün ödənişin işlənməsi
    • Biletlərin yaradılması və göndərilməsi
    • Rezervasyonların ləğv edilməsi (vaxt məhdudiyyəti ilə)
    • Müxtəlif yer növləri dəstəyi (standart, premium və s.)
    • Endirimlər və promosiyaların tətbiqi
  2. Qeyri-Funksional Tələblər:

    • Concurrent rezervasiya dəstəyi
    • Pik vaxtlarda yüksək availability
    • Sürətli axtarış və rezervasiya
    • Güvenli ödəniş işlənməsi
    • Scalability

Əsas Komponentlər

  1. Kino Kompleksi: Filmlərin göstərildiyi yerlərin idarəsi
  2. Film İdarəetməsi: Film məlumatlarının saxlanması
  3. Seans İdarəetməsi: Seans vaxtları və məkan planlaması
  4. Rezervasiya Sistemi: Yer rezervasyonları idarəsi
  5. Ödəniş Sistemi: Ödəniş işlənməsi və bilet yaradılması

Dizayn Yanaşması

Bilet rezervasiya prosesinə həsr olunmuş State Pattern istifadə edəcəyik:

  1. Available: Yer mövcuddur
  2. Reserved: Yer müvəqqəti rezerv edilib
  3. Booked: Yer həqiqətən alınıb
  4. Blocked: Yer müvəqqəti bloklanıb (təmizlik, təmir və s.)

İcra

Koda bax
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentHashMap;

// Seat types
enum SeatType {
STANDARD(new BigDecimal("10.00")),
PREMIUM(new BigDecimal("15.00")),
VIP(new BigDecimal("25.00"));

private final BigDecimal basePrice;

SeatType(BigDecimal basePrice) {
this.basePrice = basePrice;
}

public BigDecimal getBasePrice() {
return basePrice;
}
}

// Seat status
enum SeatStatus {
AVAILABLE, RESERVED, BOOKED, BLOCKED
}

// Movie genres
enum Genre {
ACTION, COMEDY, DRAMA, HORROR, ROMANCE, THRILLER, DOCUMENTARY
}

// Seat class
class Seat {
private final String seatId;
private final int row;
private final int column;
private final SeatType type;
private SeatStatus status;
private final Lock lock;
private String reservedBy;
private LocalDateTime reservationTime;

public Seat(String seatId, int row, int column, SeatType type) {
this.seatId = seatId;
this.row = row;
this.column = column;
this.type = type;
this.status = SeatStatus.AVAILABLE;
this.lock = new ReentrantLock();
}

public String getSeatId() { return seatId; }
public int getRow() { return row; }
public int getColumn() { return column; }
public SeatType getType() { return type; }
public SeatStatus getStatus() { return status; }
public String getReservedBy() { return reservedBy; }

public boolean reserve(String userId) {
lock.lock();
try {
if (status == SeatStatus.AVAILABLE) {
status = SeatStatus.RESERVED;
reservedBy = userId;
reservationTime = LocalDateTime.now();
return true;
}
return false;
} finally {
lock.unlock();
}
}

public boolean book(String userId) {
lock.lock();
try {
if (status == SeatStatus.RESERVED && userId.equals(reservedBy)) {
status = SeatStatus.BOOKED;
return true;
}
return false;
} finally {
lock.unlock();
}
}

public boolean release(String userId) {
lock.lock();
try {
if ((status == SeatStatus.RESERVED || status == SeatStatus.BOOKED)
&& userId.equals(reservedBy)) {
status = SeatStatus.AVAILABLE;
reservedBy = null;
reservationTime = null;
return true;
}
return false;
} finally {
lock.unlock();
}
}

public boolean isReservationExpired(Duration reservationTimeout) {
lock.lock();
try {
if (status == SeatStatus.RESERVED && reservationTime != null) {
return LocalDateTime.now().isAfter(reservationTime.plus(reservationTimeout));
}
return false;
} finally {
lock.unlock();
}
}

public void expireReservation() {
lock.lock();
try {
if (status == SeatStatus.RESERVED) {
status = SeatStatus.AVAILABLE;
reservedBy = null;
reservationTime = null;
}
} finally {
lock.unlock();
}
}

@Override
public String toString() {
return String.format("Seat{%s, Row: %d, Col: %d, Type: %s, Status: %s}",
seatId, row, column, type, status);
}
}

// Movie class
class Movie {
private final String movieId;
private final String title;
private final String description;
private final Duration duration;
private final Genre genre;
private final String director;
private final List<String> actors;
private final String language;
private final LocalDateTime releaseDate;

public Movie(String movieId, String title, String description, Duration duration,
Genre genre, String director, List<String> actors, String language,
LocalDateTime releaseDate) {
this.movieId = movieId;
this.title = title;
this.description = description;
this.duration = duration;
this.genre = genre;
this.director = director;
this.actors = new ArrayList<>(actors);
this.language = language;
this.releaseDate = releaseDate;
}

// Getters
public String getMovieId() { return movieId; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public Duration getDuration() { return duration; }
public Genre getGenre() { return genre; }
public String getDirector() { return director; }
public List<String> getActors() { return new ArrayList<>(actors); }
public String getLanguage() { return language; }
public LocalDateTime getReleaseDate() { return releaseDate; }

@Override
public String toString() {
return String.format("Movie{%s: %s (%s, %d dk)}",
movieId, title, genre, duration.toMinutes());
}
}

// Theater hall class
class TheaterHall {
private final String hallId;
private final String name;
private final int totalSeats;
private final Map<String, Seat> seats;

public TheaterHall(String hallId, String name, int rows, int seatsPerRow) {
this.hallId = hallId;
this.name = name;
this.totalSeats = rows * seatsPerRow;
this.seats = new ConcurrentHashMap<>();

initializeSeats(rows, seatsPerRow);
}

private void initializeSeats(int rows, int seatsPerRow) {
for (int row = 1; row <= rows; row++) {
for (int col = 1; col <= seatsPerRow; col++) {
String seatId = String.format("%s-R%d-C%d", hallId, row, col);
SeatType type = determineSeatType(row, rows);
seats.put(seatId, new Seat(seatId, row, col, type));
}
}
}

private SeatType determineSeatType(int row, int totalRows) {
if (row <= 2) return SeatType.PREMIUM;
if (row >= totalRows - 1) return SeatType.VIP;
return SeatType.STANDARD;
}

public String getHallId() { return hallId; }
public String getName() { return name; }
public int getTotalSeats() { return totalSeats; }

public List<Seat> getAvailableSeats() {
return seats.values().stream()
.filter(seat -> seat.getStatus() == SeatStatus.AVAILABLE)
.toList();
}

public List<Seat> getAllSeats() {
return new ArrayList<>(seats.values());
}

public Seat getSeat(String seatId) {
return seats.get(seatId);
}

public Map<SeatStatus, Long> getSeatStatusCount() {
return seats.values().stream()
.collect(Collectors.groupingBy(Seat::getStatus, Collectors.counting()));
}

public void cleanupExpiredReservations(Duration reservationTimeout) {
seats.values().parallelStream()
.filter(seat -> seat.isReservationExpired(reservationTimeout))
.forEach(Seat::expireReservation);
}
}

// Show time class
class ShowTime {
private final String showId;
private final String movieId;
private final String hallId;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
private final BigDecimal basePrice;
private final Map<String, String> seatReservations; // seatId -> userId
private final Lock lock;

public ShowTime(String showId, String movieId, String hallId,
LocalDateTime startTime, Duration movieDuration, BigDecimal basePrice) {
this.showId = showId;
this.movieId = movieId;
this.hallId = hallId;
this.startTime = startTime;
this.endTime = startTime.plus(movieDuration).plus(Duration.ofMinutes(30)); // Buffer time
this.basePrice = basePrice;
this.seatReservations = new ConcurrentHashMap<>();
this.lock = new ReentrantLock();
}

public String getShowId() { return showId; }
public String getMovieId() { return movieId; }
public String getHallId() { return hallId; }
public LocalDateTime getStartTime() { return startTime; }
public LocalDateTime getEndTime() { return endTime; }
public BigDecimal getBasePrice() { return basePrice; }

public boolean isShowActive() {
LocalDateTime now = LocalDateTime.now();
return now.isBefore(startTime) || (now.isAfter(startTime) && now.isBefore(endTime));
}

public BigDecimal calculatePrice(SeatType seatType) {
return basePrice.add(seatType.getBasePrice());
}

@Override
public String toString() {
return String.format("ShowTime{%s: Film %s, Salon %s, Başlanğıc %s}",
showId, movieId, hallId, startTime);
}
}

// Booking class
class Booking {
private final String bookingId;
private final String userId;
private final String showId;
private final List<String> seatIds;
private final BigDecimal totalAmount;
private final LocalDateTime bookingTime;
private BookingStatus status;
private String paymentId;

public Booking(String bookingId, String userId, String showId,
List<String> seatIds, BigDecimal totalAmount) {
this.bookingId = bookingId;
this.userId = userId;
this.showId = showId;
this.seatIds = new ArrayList<>(seatIds);
this.totalAmount = totalAmount;
this.bookingTime = LocalDateTime.now();
this.status = BookingStatus.PENDING;
}

public String getBookingId() { return bookingId; }
public String getUserId() { return userId; }
public String getShowId() { return showId; }
public List<String> getSeatIds() { return new ArrayList<>(seatIds); }
public BigDecimal getTotalAmount() { return totalAmount; }
public LocalDateTime getBookingTime() { return bookingTime; }
public BookingStatus getStatus() { return status; }
public String getPaymentId() { return paymentId; }

public void setStatus(BookingStatus status) {
this.status = status;
}

public void setPaymentId(String paymentId) {
this.paymentId = paymentId;
}

@Override
public String toString() {
return String.format("Booking{%s: User %s, Show %s, Seats: %s, Amount: $%.2f, Status: %s}",
bookingId, userId, showId, seatIds, totalAmount, status);
}
}

// Booking status
enum BookingStatus {
PENDING, CONFIRMED, CANCELLED, EXPIRED
}

// Payment result
class PaymentResult {
private final boolean success;
private final String paymentId;
private final String message;

public PaymentResult(boolean success, String paymentId, String message) {
this.success = success;
this.paymentId = paymentId;
this.message = message;
}

public boolean isSuccess() { return success; }
public String getPaymentId() { return paymentId; }
public String getMessage() { return message; }
}

// Payment service
interface PaymentService {
PaymentResult processPayment(String userId, BigDecimal amount, String description);
boolean refundPayment(String paymentId, BigDecimal amount);
}

class MockPaymentService implements PaymentService {
@Override
public PaymentResult processPayment(String userId, BigDecimal amount, String description) {
// Simulate payment processing
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

// Simulate 90% success rate
boolean success = Math.random() > 0.1;
String paymentId = success ? "PAY-" + System.currentTimeMillis() : null;
String message = success ? "Ödəniş uğurlu" : "Ödəniş uğursuz";

return new PaymentResult(success, paymentId, message);
}

@Override
public boolean refundPayment(String paymentId, BigDecimal amount) {
// Simulate refund processing
return paymentId != null && Math.random() > 0.05; // 95% success rate
}
}

// Movie ticket booking service
class MovieTicketBookingService {
private final Map<String, Movie> movies;
private final Map<String, TheaterHall> halls;
private final Map<String, ShowTime> showTimes;
private final Map<String, Booking> bookings;
private final PaymentService paymentService;
private final Duration reservationTimeout;

public MovieTicketBookingService() {
this.movies = new ConcurrentHashMap<>();
this.halls = new ConcurrentHashMap<>();
this.showTimes = new ConcurrentHashMap<>();
this.bookings = new ConcurrentHashMap<>();
this.paymentService = new MockPaymentService();
this.reservationTimeout = Duration.ofMinutes(15);

// Start background task to cleanup expired reservations
startReservationCleanupTask();
}

public void addMovie(Movie movie) {
movies.put(movie.getMovieId(), movie);
}

public void addTheaterHall(TheaterHall hall) {
halls.put(hall.getHallId(), hall);
}

public void addShowTime(ShowTime showTime) {
showTimes.put(showTime.getShowId(), showTime);
}

public List<Movie> searchMovies(String query) {
return movies.values().stream()
.filter(movie -> movie.getTitle().toLowerCase().contains(query.toLowerCase()) ||
movie.getDescription().toLowerCase().contains(query.toLowerCase()))
.toList();
}

public List<ShowTime> getShowTimesForMovie(String movieId) {
return showTimes.values().stream()
.filter(show -> show.getMovieId().equals(movieId) && show.isShowActive())
.sorted(Comparator.comparing(ShowTime::getStartTime))
.toList();
}

public List<Seat> getAvailableSeats(String showId) {
ShowTime showTime = showTimes.get(showId);
if (showTime != null) {
TheaterHall hall = halls.get(showTime.getHallId());
if (hall != null) {
// Cleanup expired reservations first
hall.cleanupExpiredReservations(reservationTimeout);
return hall.getAvailableSeats();
}
}
return new ArrayList<>();
}

public synchronized String reserveSeats(String userId, String showId, List<String> seatIds) {
ShowTime showTime = showTimes.get(showId);
if (showTime == null || !showTime.isShowActive()) {
throw new IllegalArgumentException("Yanlış və ya aktiv olmayan seans");
}

TheaterHall hall = halls.get(showTime.getHallId());
if (hall == null) {
throw new IllegalArgumentException("Salon tapılmadı");
}

// Try to reserve all seats
List<Seat> reservedSeats = new ArrayList<>();
for (String seatId : seatIds) {
Seat seat = hall.getSeat(seatId);
if (seat == null) {
// Release already reserved seats
reservedSeats.forEach(s -> s.release(userId));
throw new IllegalArgumentException("Yanlış yer: " + seatId);
}

if (!seat.reserve(userId)) {
// Release already reserved seats
reservedSeats.forEach(s -> s.release(userId));
throw new IllegalArgumentException("Yer artıq rezerv olunub: " + seatId);
}
reservedSeats.add(seat);
}

// Calculate total amount
BigDecimal totalAmount = reservedSeats.stream()
.map(seat -> showTime.calculatePrice(seat.getType()))
.reduce(BigDecimal.ZERO, BigDecimal::add);

// Create booking
String bookingId = "BOOK-" + System.currentTimeMillis();
Booking booking = new Booking(bookingId, userId, showId, seatIds, totalAmount);
bookings.put(bookingId, booking);

return bookingId;
}

public boolean confirmBooking(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking == null || booking.getStatus() != BookingStatus.PENDING) {
return false;
}

// Process payment
PaymentResult paymentResult = paymentService.processPayment(
booking.getUserId(),
booking.getTotalAmount(),
"Kino bilet ödənişi - Rezervasiya: " + bookingId
);

if (!paymentResult.isSuccess()) {
// Payment failed, release seats
cancelBooking(bookingId);
return false;
}

// Payment successful, confirm booking
booking.setPaymentId(paymentResult.getPaymentId());
booking.setStatus(BookingStatus.CONFIRMED);

// Book the seats
ShowTime showTime = showTimes.get(booking.getShowId());
TheaterHall hall = halls.get(showTime.getHallId());

for (String seatId : booking.getSeatIds()) {
Seat seat = hall.getSeat(seatId);
seat.book(booking.getUserId());
}

return true;
}

public boolean cancelBooking(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking == null) {
return false;
}

// Release seats
ShowTime showTime = showTimes.get(booking.getShowId());
if (showTime != null) {
TheaterHall hall = halls.get(showTime.getHallId());
if (hall != null) {
for (String seatId : booking.getSeatIds()) {
Seat seat = hall.getSeat(seatId);
if (seat != null) {
seat.release(booking.getUserId());
}
}
}
}

// Process refund if payment was made
if (booking.getStatus() == BookingStatus.CONFIRMED && booking.getPaymentId() != null) {
paymentService.refundPayment(booking.getPaymentId(), booking.getTotalAmount());
}

booking.setStatus(BookingStatus.CANCELLED);
return true;
}

public List<Booking> getUserBookings(String userId) {
return bookings.values().stream()
.filter(booking -> booking.getUserId().equals(userId))
.sorted(Comparator.comparing(Booking::getBookingTime).reversed())
.toList();
}

public Booking getBooking(String bookingId) {
return bookings.get(bookingId);
}

private void startReservationCleanupTask() {
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
cleanupExpiredReservations();
}
}, 60000, 60000); // Run every minute
}

private void cleanupExpiredReservations() {
// Cleanup expired seat reservations
halls.values().parallelStream()
.forEach(hall -> hall.cleanupExpiredReservations(reservationTimeout));

// Cancel expired bookings
LocalDateTime expiredTime = LocalDateTime.now().minus(reservationTimeout);
bookings.values().parallelStream()
.filter(booking -> booking.getStatus() == BookingStatus.PENDING &&
booking.getBookingTime().isBefore(expiredTime))
.forEach(booking -> {
booking.setStatus(BookingStatus.EXPIRED);
cancelBooking(booking.getBookingId());
});
}
}

// Example usage
public class MovieTicketBookingDemo {
public static void main(String[] args) {
MovieTicketBookingService bookingService = new MovieTicketBookingService();

// Add movies
Movie movie1 = new Movie("M001", "Avengers: Endgame", "Superhero epic finale",
Duration.ofMinutes(181), Genre.ACTION, "Russo Brothers",
Arrays.asList("Robert Downey Jr.", "Chris Evans", "Scarlett Johansson"),
"English", LocalDateTime.now().minusMonths(6));

Movie movie2 = new Movie("M002", "The Batman", "Dark knight returns",
Duration.ofMinutes(176), Genre.ACTION, "Matt Reeves",
Arrays.asList("Robert Pattinson", "Zoë Kravitz", "Paul Dano"),
"English", LocalDateTime.now().minusMonths(3));

bookingService.addMovie(movie1);
bookingService.addMovie(movie2);

// Add theater halls
TheaterHall hall1 = new TheaterHall("H001", "IMAX Salon 1", 10, 15);
TheaterHall hall2 = new TheaterHall("H002", "Standard Salon 2", 8, 12);

bookingService.addTheaterHall(hall1);
bookingService.addTheaterHall(hall2);

// Add show times
LocalDateTime showTime1 = LocalDateTime.now().plusHours(2);
LocalDateTime showTime2 = LocalDateTime.now().plusHours(5);

ShowTime show1 = new ShowTime("S001", "M001", "H001", showTime1,
movie1.getDuration(), new BigDecimal("15.00"));
ShowTime show2 = new ShowTime("S002", "M002", "H002", showTime2,
movie2.getDuration(), new BigDecimal("12.00"));

bookingService.addShowTime(show1);
bookingService.addShowTime(show2);

// Search movies
System.out.println("Filmlər axtarışı 'Batman':");
List<Movie> searchResults = bookingService.searchMovies("Batman");
searchResults.forEach(System.out::println);

// Get show times for a movie
System.out.println("\nBatman filmi üçün seanslar:");
List<ShowTime> showTimes = bookingService.getShowTimesForMovie("M002");
showTimes.forEach(System.out::println);

// Get available seats
System.out.println("\nMövcud yerlər S002 seansı üçün:");
List<Seat> availableSeats = bookingService.getAvailableSeats("S002");
availableSeats.stream().limit(10).forEach(System.out::println);

// Reserve seats
try {
String bookingId = bookingService.reserveSeats("user1", "S002",
Arrays.asList("H002-R5-C5", "H002-R5-C6"));
System.out.println("\nYerlər rezerv edildi. Rezervasiya ID: " + bookingId);

// Confirm booking
boolean confirmed = bookingService.confirmBooking(bookingId);
System.out.println("Rezervasiya təsdiqləndi: " + confirmed);

// Get booking details
Booking booking = bookingService.getBooking(bookingId);
System.out.println("Rezervasiya detalları: " + booking);

} catch (Exception e) {
System.err.println("Rezervasiya xətası: " + e.getMessage());
}
}
}

Thread Safety Considerations

  1. ReentrantLock: Hər yer üçün ayrı lock istifadə olunur
  2. ConcurrentHashMap: Thread-safe məlumat strukturları
  3. Synchronized Methods: Kritik əməliyyatlar üçün sinxronizasiya
  4. Atomic Operations: Yer rezervasyonu atomik əməliyyatlar ilə

Concurrency Handling

  1. Seat Reservation: Eyni yer üçün çoxlu sorğu halında thread safety
  2. Expired Reservation Cleanup: Arxa fon tapşırığı ilə vaxtı keçmiş rezervasiyaların təmizlənməsi
  3. Payment Processing: Ödəniş prosesinin təhlükəsiz işlənməsi
  4. Booking State Management: Rezervasiya vəziyyətlərinin tutarlı idarəsi

Business Logic

  1. Seat Types: Müxtəlif yer növləri və qiymət hesablama
  2. Show Scheduling: Seans planlaması və vaxt idarəsi
  3. Payment Integration: Ödəniş sistemi inteqrasiyası
  4. Booking Lifecycle: Rezervasyonun tam həyat dövrü

Additional Features

  1. Search Functionality: Film axtarış imkanları
  2. Discount System: Endirimlər və promosiyalar
  3. Notification System: İstifadəçilərə bildirişlər
  4. Refund Processing: Ləğv edilmiş rezervasyonlar üçün geri ödəmə