Design a Movie Ticket Booking System

muhammad_salem

Muhammad Salem

Posted on July 20, 2024

Design a Movie Ticket Booking System

Design a Movie Ticket Booking System
An online movie ticket booking system facilitates the purchasing of movie tickets to its customers. E-ticketing systems allow customers to browse through movies currently playing and book seats, anywhere and anytime.

Our ticket booking service should meet the following requirements:

It should be able to list the cities where affiliate cinemas are located.
Each cinema can have multiple halls and each hall can run one movie show at a time.
Each Movie will have multiple shows.
Customers should be able to search movies by their title, language, genre, release date, and city name.
Once the customer selects a movie, the service should display the cinemas running that movie and its available shows.
The customer should be able to select a show at a particular cinema and book their tickets.
The service should show the customer the seating arrangement of the cinema hall. The customer should be able to select multiple seats according to their preference.
The customer should be able to distinguish between available seats and booked ones.
The system should send notifications whenever there is a new movie, as well as when a booking is made or canceled.
Customers of our system should be able to pay with credit cards or cash.
The system should ensure that no two customers can reserve the same seat.
Customers should be able to add a discount coupon to their payment.
Use case diagram
We have five main Actors in our system:

Admin: Responsible for adding new movies and their shows, canceling any movie or show, blocking/unblocking customers, etc.
FrontDeskOfficer: Can book/cancel tickets.
Customer: Can view movie schedules, book, and cancel tickets.
Guest: All guests can search movies but to book seats they have to become a registered member.
System: Mainly responsible for sending notifications for new movies, bookings, cancellations, etc.
Here are the top use cases of the Movie Ticket Booking System:

Search movies: To search movies by title, genre, language, release date, and city name.
Create/Modify/View booking: To book a movie show ticket, cancel it or view details about the show.
Make payment for booking: To pay for the booking.
Add a coupon to the payment: To add a discount coupon to the payment.
Assign Seat: Customers will be shown a seat map to let them select seats for their booking.
Refund payment: Upon cancellation, customers will be refunded the payment amount as long as the cancellation occurs within the allowed time frame.

Let's begin with the system design.

  1. System Components and Class Hierarchy

First, let's identify the core components of our system:

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Cinema> Cinemas { get; set; }
}

public class Cinema
{
    public int Id { get; set; }
    public string Name { get; set; }
    public City City { get; set; }
    public List<CinemaHall> Halls { get; set; }
}

public class CinemaHall
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int TotalSeats { get; set; }
    public Cinema Cinema { get; set; }
    public List<Show> Shows { get; set; }
}

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int DurationInMinutes { get; set; }
    public string Language { get; set; }
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; }
    public List<Show> Shows { get; set; }
}

public class Show
{
    public int Id { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    public CinemaHall CinemaHall { get; set; }
    public Movie Movie { get; set; }
    public List<Seat> Seats { get; set; }
}

public class Seat
{
    public int Id { get; set; }
    public int RowNumber { get; set; }
    public int ColumnNumber { get; set; }
    public SeatType Type { get; set; }
    public Show Show { get; set; }
}

public class Booking
{
    public int Id { get; set; }
    public int NumberOfSeats { get; set; }
    public DateTime Timestamp { get; set; }
    public BookingStatus Status { get; set; }
    public Show Show { get; set; }
    public User User { get; set; }
    public List<Seat> Seats { get; set; }
    public Payment Payment { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public List<Booking> Bookings { get; set; }
}

public class Payment
{
    public int Id { get; set; }
    public decimal Amount { get; set; }
    public DateTime Timestamp { get; set; }
    public PaymentStatus Status { get; set; }
    public PaymentMethod Method { get; set; }
    public Booking Booking { get; set; }
    public Coupon AppliedCoupon { get; set; }
}

public class Coupon
{
    public int Id { get; set; }
    public string Code { get; set; }
    public decimal Discount { get; set; }
    public DateTime ExpirationDate { get; set; }
}

public enum SeatType
{
    Regular,
    Premium,
    Accessible
}

public enum BookingStatus
{
    Pending,
    Confirmed,
    Cancelled
}

public enum PaymentStatus
{
    Pending,
    Completed,
    Failed,
    Refunded
}

public enum PaymentMethod
{
    CreditCard,
    Cash
}
Enter fullscreen mode Exit fullscreen mode
  1. Key Services

Now, let's define the key services that will handle the business logic:

public interface IMovieService
{
    List<Movie> SearchMovies(string title, string language, string genre, DateTime? releaseDate, string cityName);
    List<Cinema> GetCinemasShowingMovie(int movieId, int cityId);
    List<Show> GetShowsForMovieAndCinema(int movieId, int cinemaId);
}

public interface IBookingService
{
    Booking CreateBooking(int userId, int showId, List<int> seatIds);
    void CancelBooking(int bookingId);
    Booking GetBookingDetails(int bookingId);
}

public interface IPaymentService
{
    Payment ProcessPayment(int bookingId, PaymentMethod method, string couponCode = null);
    void RefundPayment(int paymentId);
}

public interface ISeatAllocationService
{
    List<Seat> GetAvailableSeats(int showId);
    bool ReserveSeats(int showId, List<int> seatIds, int userId);
    void ReleaseSeats(int showId, List<int> seatIds);
}

public interface INotificationService
{
    void SendNewMovieNotification(Movie movie);
    void SendBookingConfirmation(Booking booking);
    void SendBookingCancellation(Booking booking);
}
Enter fullscreen mode Exit fullscreen mode
  1. Implementing Key Functionalities

Let's implement some key functionalities:

public class BookingService : IBookingService
{
    private readonly ISeatAllocationService _seatAllocationService;
    private readonly IPaymentService _paymentService;
    private readonly INotificationService _notificationService;

    public BookingService(ISeatAllocationService seatAllocationService, 
                          IPaymentService paymentService, 
                          INotificationService notificationService)
    {
        _seatAllocationService = seatAllocationService;
        _paymentService = paymentService;
        _notificationService = notificationService;
    }

    public Booking CreateBooking(int userId, int showId, List<int> seatIds)
    {
        // Use a transaction to ensure atomicity
        using (var transaction = new TransactionScope())
        {
            // Check if seats are available and reserve them
            if (!_seatAllocationService.ReserveSeats(showId, seatIds, userId))
            {
                throw new InvalidOperationException("Selected seats are not available.");
            }

            // Create the booking
            var booking = new Booking
            {
                UserId = userId,
                ShowId = showId,
                NumberOfSeats = seatIds.Count,
                Timestamp = DateTime.UtcNow,
                Status = BookingStatus.Pending,
                Seats = seatIds.Select(id => new Seat { Id = id }).ToList()
            };

            // Save the booking to the database
            // (Assuming we have a repository or data access layer)
            _bookingRepository.Add(booking);

            // Process the payment
            var payment = _paymentService.ProcessPayment(booking.Id, PaymentMethod.CreditCard);

            if (payment.Status == PaymentStatus.Completed)
            {
                booking.Status = BookingStatus.Confirmed;
                _bookingRepository.Update(booking);

                // Send confirmation notification
                _notificationService.SendBookingConfirmation(booking);
            }
            else
            {
                // If payment fails, release the seats
                _seatAllocationService.ReleaseSeats(showId, seatIds);
                throw new InvalidOperationException("Payment failed. Booking could not be completed.");
            }

            transaction.Complete();
            return booking;
        }
    }

    // Implement other IBookingService methods...
}
Enter fullscreen mode Exit fullscreen mode
  1. Handling Concurrency and Race Conditions

To ensure that no two customers can book the same seat, we'll use optimistic concurrency control:

public class SeatAllocationService : ISeatAllocationService
{
    private readonly IShowRepository _showRepository;

    public SeatAllocationService(IShowRepository showRepository)
    {
        _showRepository = showRepository;
    }

    public bool ReserveSeats(int showId, List<int> seatIds, int userId)
    {
        var show = _showRepository.GetById(showId);
        var seatsToReserve = show.Seats.Where(s => seatIds.Contains(s.Id)).ToList();

        if (seatsToReserve.Any(s => s.IsReserved))
        {
            return false;
        }

        foreach (var seat in seatsToReserve)
        {
            seat.IsReserved = true;
            seat.ReservedBy = userId;
        }

        try
        {
            _showRepository.Update(show);
            return true;
        }
        catch (DbUpdateConcurrencyException)
        {
            // Another user has modified the seats. Retry the operation.
            return ReserveSeats(showId, seatIds, userId);
        }
    }

    // Implement other ISeatAllocationService methods...
}
Enter fullscreen mode Exit fullscreen mode
  1. Implementing Search Functionality

To implement efficient search functionality, we'll use a combination of database indexing and caching:

public class MovieService : IMovieService
{
    private readonly IMovieRepository _movieRepository;
    private readonly ICacheService _cacheService;

    public MovieService(IMovieRepository movieRepository, ICacheService cacheService)
    {
        _movieRepository = movieRepository;
        _cacheService = cacheService;
    }

    public List<Movie> SearchMovies(string title, string language, string genre, DateTime? releaseDate, string cityName)
    {
        var cacheKey = $"MovieSearch:{title}:{language}:{genre}:{releaseDate}:{cityName}";
        var cachedResult = _cacheService.Get<List<Movie>>(cacheKey);

        if (cachedResult != null)
        {
            return cachedResult;
        }

        var movies = _movieRepository.Search(title, language, genre, releaseDate, cityName);
        _cacheService.Set(cacheKey, movies, TimeSpan.FromMinutes(10));

        return movies;
    }

    // Implement other IMovieService methods...
}
Enter fullscreen mode Exit fullscreen mode
  1. Handling Payments and Discounts

To handle payments and apply discounts, we'll use the Strategy pattern for different payment methods and the Decorator pattern for applying discounts:

public interface IPaymentStrategy
{
    Payment Process(decimal amount);
}

public class CreditCardPaymentStrategy : IPaymentStrategy
{
    public Payment Process(decimal amount)
    {
        // Process credit card payment
    }
}

public class CashPaymentStrategy : IPaymentStrategy
{
    public Payment Process(decimal amount)
    {
        // Process cash payment
    }
}

public class PaymentService : IPaymentService
{
    private readonly ICouponRepository _couponRepository;

    public PaymentService(ICouponRepository couponRepository)
    {
        _couponRepository = couponRepository;
    }

    public Payment ProcessPayment(int bookingId, PaymentMethod method, string couponCode = null)
    {
        var booking = _bookingRepository.GetById(bookingId);
        var amount = CalculateBookingAmount(booking);

        if (!string.IsNullOrEmpty(couponCode))
        {
            var coupon = _couponRepository.GetByCode(couponCode);
            if (coupon != null && coupon.IsValid())
            {
                amount -= coupon.CalculateDiscount(amount);
            }
        }

        IPaymentStrategy paymentStrategy = method == PaymentMethod.CreditCard
            ? new CreditCardPaymentStrategy()
            : new CashPaymentStrategy();

        var payment = paymentStrategy.Process(amount);
        payment.BookingId = bookingId;

        return payment;
    }

    // Implement other IPaymentService methods...
}
Enter fullscreen mode Exit fullscreen mode
  1. Scalability and Performance Considerations

To ensure our system can handle high loads, we'll implement the following:

  • Use asynchronous programming for I/O-bound operations
  • Implement database sharding based on city or cinema
  • Use a distributed cache (e.g., Redis) for frequently accessed data
  • Implement a message queue (e.g., RabbitMQ) for handling notifications asynchronously
  1. Security Considerations
  • Implement proper authentication and authorization using JWT tokens
  • Use HTTPS for all communications
  • Implement rate limiting to prevent abuse
  • Use secure payment gateways for handling sensitive payment information
  1. Testing Strategy
  • Unit tests for individual components and services
  • Integration tests for testing the interaction between different services
  • End-to-end tests for complete user workflows
  • Performance tests to ensure the system can handle expected loads

This design demonstrates a professional approach to solving the Movie Ticket Booking System problem. It shows:

  • Clear understanding of the problem domain
  • Proper use of OOP principles and design patterns
  • Consideration of concurrency and race conditions
  • Efficient search implementation
  • Flexible payment and discount system
  • Scalability and performance considerations
  • Attention to security and testing

The design is modular, extensible, and follows SOLID principles, making it easy to maintain and extend in the future.

💖 💪 🙅 🚩
muhammad_salem
Muhammad Salem

Posted on July 20, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related