Functional Scope (In-Scope)
- Dynamic Seat Layout: Model various categories (Silver, Gold, VIP) and individual seat layout allocations.
- Dynamic Weekend/Peak Pricing: Support dynamic seat pricing strategies dynamically responding to show weekend peaks.
- Two-Phase Reservations: Prevent instant double-booking. Seats are held (RESERVED) with a dynamic TTL (e.g., 10 minutes) allowing transaction flow.
- Fail-Safe Multi-Seat Rollback: Acquire multiple locks systematically (sorted to prevent deadlocks) and run rolling rollbacks if any single seat fails.
- Transactional Expiry Cleanup: Automatically release seat holds back to AVAILABLE upon payment expiration or client cancellation.
Explicit Boundaries (Out-of-Scope)
- No Third-Party Payment Integrations: External credit cards or webhooks are simulated via standard synchronous/asynchronous checkout states.
- No Real Database Integration: All storage is localized to in-memory safe data structures.
Clean reference designs showing transactional booking mechanics and thread-safety in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
enum SeatCategory { SILVER, GOLD, VIP }
enum SeatStatus { AVAILABLE, RESERVED, BOOKED }
enum BookingState { PENDING, CONFIRMED, CANCELLED, EXPIRED }
interface PricingStrategy {
double calculatePrice(SeatCategory category, boolean isWeekend);
}
class DynamicPricingStrategy implements PricingStrategy {
@Override
public double calculatePrice(SeatCategory category, boolean isWeekend) {
double base = switch (category) {
case SILVER -> 100.0;
case GOLD -> 150.0;
case VIP -> 250.0;
};
return isWeekend ? base * 1.25 : base;
}
}
class Seat {
private final String id;
private final SeatCategory category;
private SeatStatus status = SeatStatus.AVAILABLE;
private long reservationTime = 0;
private final ReentrantLock lock = new ReentrantLock();
public Seat(String id, SeatCategory category) {
this.id = id;
this.category = category;
}
public String getId() { return id; }
public SeatCategory getCategory() { return category; }
public ReentrantLock getLock() { return lock; }
public SeatStatus getStatus(long ttlMs) {
if (status == SeatStatus.RESERVED && (System.currentTimeMillis() - reservationTime > ttlMs)) {
status = SeatStatus.AVAILABLE;
reservationTime = 0;
}
return status;
}
public boolean reserve(long ttlMs) {
if (getStatus(ttlMs) == SeatStatus.AVAILABLE) {
status = SeatStatus.RESERVED;
reservationTime = System.currentTimeMillis();
return true;
}
return false;
}
public void book() {
if (status == SeatStatus.RESERVED) {
status = SeatStatus.BOOKED;
reservationTime = 0;
}
}
public void release() {
if (status == SeatStatus.RESERVED) {
status = SeatStatus.AVAILABLE;
reservationTime = 0;
}
}
}
class Show {
private final String id;
private final String movie;
private final boolean isWeekend;
private final Map<String, Seat> seats = new ConcurrentHashMap<>();
public Show(String id, String movie, boolean isWeekend, int silverCount, int goldCount, int vipCount) {
this.id = id;
this.movie = movie;
this.isWeekend = isWeekend;
int idx = 1;
for (int i = 0; i < silverCount; i++) {
String sid = "S" + idx++;
seats.put(sid, new Seat(sid, SeatCategory.SILVER));
}
for (int i = 0; i < goldCount; i++) {
String sid = "S" + idx++;
seats.put(sid, new Seat(sid, SeatCategory.GOLD));
}
for (int i = 0; i < vipCount; i++) {
String sid = "S" + idx++;
seats.put(sid, new Seat(sid, SeatCategory.VIP));
}
}
public String getId() { return id; }
public String getMovie() { return movie; }
public boolean isWeekend() { return isWeekend; }
public Map<String, Seat> getSeats() { return seats; }
}
class Booking {
private final String id;
private final Show show;
private final List<Seat> seats;
private final double amount;
private BookingState state = BookingState.PENDING;
private final long createdAt;
public Booking(String id, Show show, List<Seat> seats, double amount) {
this.id = id;
this.show = show;
this.seats = seats;
this.amount = amount;
this.createdAt = System.currentTimeMillis();
}
public String getId() { return id; }
public Show getShow() { return show; }
public List<Seat> getSeats() { return seats; }
public double getAmount() { return amount; }
public BookingState getState() { return state; }
public long getCreatedAt() { return createdAt; }
public synchronized void confirm() {
if (state == BookingState.PENDING) {
state = BookingState.CONFIRMED;
for (Seat seat : seats) {
seat.book();
}
}
}
public synchronized void cancel(BookingState reason) {
if (state == BookingState.PENDING) {
state = reason;
for (Seat seat : seats) {
seat.release();
}
}
}
}
class BookingService {
private final Map<String, Booking> bookings = new ConcurrentHashMap<>();
private final PricingStrategy pricingStrategy = new DynamicPricingStrategy();
private final long ttlMs;
public BookingService(long ttlMs) {
this.ttlMs = ttlMs;
}
public Booking createBooking(Show show, List<String> seatIds) {
List<Seat> targetSeats = new ArrayList<>();
for (String id : seatIds) {
Seat s = show.getSeats().get(id);
if (s == null) return null;
targetSeats.add(s);
}
// Lock sorting to prevent Deadlocks
targetSeats.sort(Comparator.comparing(Seat::getId));
List<Seat> acquired = new ArrayList<>();
boolean success = true;
for (Seat seat : targetSeats) {
seat.getLock().lock();
try {
if (seat.reserve(ttlMs)) {
acquired.add(seat);
} else {
success = false;
break;
}
} finally {
seat.getLock().unlock();
}
}
if (!success) {
for (Seat seat : acquired) {
seat.getLock().lock();
try {
seat.release();
} finally {
seat.getLock().unlock();
}
}
return null;
}
double totalAmount = 0;
for (Seat seat : targetSeats) {
totalAmount += pricingStrategy.calculatePrice(seat.getCategory(), show.isWeekend());
}
String bookingId = "BKG-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
Booking booking = new Booking(bookingId, show, targetSeats, totalAmount);
bookings.put(bookingId, booking);
return booking;
}
public boolean confirmBooking(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking == null) return false;
synchronized (booking) {
if (booking.getState() != BookingState.PENDING) return false;
if (System.currentTimeMillis() - booking.getCreatedAt() > ttlMs) {
booking.cancel(BookingState.EXPIRED);
return false;
}
booking.confirm();
return true;
}
}
public void cancelBooking(String bookingId) {
Booking booking = bookings.get(bookingId);
if (booking == null) return;
synchronized (booking) {
booking.cancel(BookingState.CANCELLED);
}
}
public void cleanupExpiredBookings() {
for (Booking booking : bookings.values()) {
synchronized (booking) {
if (booking.getState() == BookingState.PENDING &&
(System.currentTimeMillis() - booking.getCreatedAt() > ttlMs)) {
booking.cancel(BookingState.EXPIRED);
}
}
}
}
}
// ─── DRIVER CLASS ──────────────────────────────────────────────────────────
public class BMSDriver {
public static void main(String[] args) throws Exception {
System.out.println("=== MOVIE TICKET BOOKING (BMS) SIMULATION ===");
BookingService service = new BookingService(1500); // 1.5 seconds TTL for fast demo
Show avengersWeekend = new Show("SHOW-101", "Avengers: Endgame", true, 5, 3, 2);
System.out.println("Show Created: " + avengersWeekend.getMovie() + " (Weekend: " + avengersWeekend.isWeekend() + ")");
// User A tries to reserve S1, S2
Booking bookingA = service.createBooking(avengersWeekend, Arrays.asList("S1", "S2"));
System.out.println("User A Reserved S1, S2 -> Booking ID: " + bookingA.getId() + ", Amount: $" + bookingA.getAmount());
// Concurrent Thread trying to reserve S2, S3 (should fail due to S2 being reserved)
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Booking> userBTask = executor.submit(() -> service.createBooking(avengersWeekend, Arrays.asList("S2", "S3")));
Booking bookingB = userBTask.get();
System.out.println("User B Attempt to reserve S2, S3 -> Result: " + (bookingB != null ? bookingB.getId() : "FAILED (Conflict - Rollback worked)"));
// User A pays and confirms booking
boolean confirmedA = service.confirmBooking(bookingA.getId());
System.out.println("User A Payment Confirmation -> " + (confirmedA ? "SUCCESS" : "FAILED"));
// User C reserves S4, S5
Booking bookingC = service.createBooking(avengersWeekend, Arrays.asList("S4", "S5"));
System.out.println("User C Reserved S4, S5 -> Booking ID: " + bookingC.getId() + ", Pending Payment...");
// Wait to simulate TTL expiry
System.out.println("Waiting 2 seconds for User C's hold to expire...");
Thread.sleep(2000);
service.cleanupExpiredBookings();
System.out.println("Booking C State: " + bookingC.getState());
// User D tries S4, S5 now
Booking bookingD = service.createBooking(avengersWeekend, Arrays.asList("S4", "S5"));
System.out.println("User D tries S4, S5 (after C expired) -> Result: " + (bookingD != null ? "SUCCESS (Booking ID: " + bookingD.getId() + ")" : "FAILED"));
executor.shutdown();
}
}