Ə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ə