Hotel Reservation System (Hotels.com, Airbnb, Expedia)

Functional Requirements

Non-functional Requirements

Overview

A hotel reservation system is a software application that allows hotels to manage and track bookings, availability, and guest information. This kind of application has design challenges which are very similar to systems such as a restaurant reservation system (OpenTable), a flight booking system (Expedia), or a movie ticket booking system (Fandango).

API Design

The client primarily sends requests about hotels, rooms, and reservations. These requests can be modeled using REST.Hotel APIs
APIParams / Request BodyDescription
GET /hotelscheckin_date, checkout_date, locationGet a list of hotels matching the search criteria. The minimum criteria usually includes a check-in date, a check-out date, and a location. It may also be necessary to filter by price or hotel stars depending on the requirements.
GET /hotel/:hotel_id Get details about a hotel with a unique hotel_id.
POST /hotelsname, locationAdd a new hotel or franchise location to the existing database of hotels. Some basic info to include would be the hotel name and location (hotel admin only)
PUT /hotels/:hotel_idname, location - all optionalUpdate hotel information for a given hotel_id. The name and location are all possible fields to update. (hotel admin only)
DELETE /hotels/:hotel_id Delete an existing hotel. (hotel admin only)
Room APIs
APIParams / Request BodyDescription
GET /hotels/:hotel_id/roomscheckin_date, checkout_dateGet the list of rooms available for reservation in a hotel, given the hotel_id, check-in date, and check-out date.
GET /hotels/:hotel_id/rooms/:room_id Get details about a specific room, given the hotel_id and room_id.
POST /hotels/:hotel_id/roomstype, pricesAdd a room to a hotel, given the hotel_id. Some important data to include for a room is its type and the prices on different days. (hotel admin only)
PUT /hotels/:hotel_id/rooms/:room_id Update room information for a given hotel_id and room_id. (hotel admin only)
DELETE /hotels/:hotel_id/rooms/:room_id Delete an existing hotel room. (hotel admin only)
Reservation APIs
APIParams / Request BodyDescription
GET /reservationsuser_id, date (optional)Get the list of reservations for a given user, or the user's history of reservations up to a certain date.
GET /reservations/:reservation_id Get details about a reservation, given its unique reservation_id.
POST /reservationsreservation infoMake a new reservation, with the request body containing relevant info needed for making a reservation, such as customer information and room information.
DELETE /reservations/:reservation_id Delete an existing reservation.
Below are examples of how the data might be displayed.
Hotel Object
hotel_id: string - PK
name: string
location: string
RoomType Object
room_type_id: string - PK
hotel_id: string
type: string
rate: integer
RoomTypeInventory Object
hotel_id: string - PK
room_type_id: string - PK
date: datetime
total_inventory: integer
total_reserved: integer
Room Object
room_id: string - PK
hotel_id: string
room_type_id: string
number: integer
Reservation Object
reservation_id: string - PK
room_type_id: string
guest_id: string
start_date: datetime
end_date: datetime
room_id: string (optional)
Guest Object
guest_id: string - PK
first_name: string
last_name: string
email: string
phone: string
There are some nuances to the RoomTypeInventory and Reservation objects.First, the Reservation object should first reserve a type of room, instead of a specific room. The specific room is usually assigned when a guest checks in at the hotel. Later on once the guest checks in at the hotel, the Reservation object can be updated to include the room_id.

How do we check whether or not a room is available?

This is the purpose of the RoomTypeInventory Table. It allows for quick queries when checking for room availability. The alternative is to query the Reservation Table by room_type_id and count the number of reservations that fall within a specific date range, which can become slow and expensive to query for at scale. Having one row per date would allow for simpler date range querying. Therefore for each year, each RoomType object would have 365 corresponding rows in the RoomTypeInventory Table.
The core design challenge unique to this problem are the concurrency issues:
  1. How do we prevent users from submitting the same reservation multiple times by accident?In the UI, there most likely would be a submit button for a user to submit their reservation request. Aside from disabling the button to prevent user error on the frontend side, there are a few ways to prevent this from the backend:
    1. Rate limiting - Rate limiting is a technique that involves limiting the number of requests that a user can make within a certain time period. For example, we could limit users to making one booking request per minute.
    2. Idempotent API Design - Idempotency is maintained if the same request produces the same result every time. This can usually implemented by including an idempotency key in the request params/body. For example, the reservation_id can be used as an idempotency key for the POST /reservations endpoint. An earlier step in the user's reservation flow can send a request to initialize a Reservation object with a new reservation_id to be used for submitting the reservation (POST /reservations).
    3. CAPTCHA - A CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a challenge-response test that can be used to determine whether a request is being made by a human or a computer. By requiring users to complete a CAPTCHA before making a booking request, we can help prevent automated bots from making multiple requests.
  2. What happens when multiple users book the same room at the same date, at the same time?This is known as the double-booking issue. Handling this requires a database that supports concurrency control mechanisms or constraints, which are common in relational database technologies, like PostgreSQL or MySQL.
    1. Pessimistic locking - When a user attempts to access or update a resource, the database system acquires a lock on the resource, preventing any other users from updating it until the lock is released (when a transaction is committed).
    2. Optimistic concurrency control - We can add a "version" column to the Reservation objects. Before modifying a Reservation, the database can read the version number. After it finishes preparing the update, the database can check the version number again to make sure it hasn't changed. Once it confirms the version number hasn't been incremented, the database applies its own increment to the version number and commits the updated transaction.
    3. Database constraints - This is similar to optimistic concurrency control. Constraints can be applied to any column or table. For example, this constraint can be added to the RoomTypeInventory table:

      CHECK(total_inventory - total_reserved >= 0)

      An update to RoomTypeInventory would occur during a POST /reservations request. If any update fails to meet this constraint, then the update to the corresponding RoomTypeInventory object would fail, which would make the request fail as expected.
    Pessimistic LockingOptimistic Concurrency ControlDatabase Constraints
    ProsEasy to implement, avoids conflicts by serializing updatesNo locking, prevents editing stale dataEasy to implement, same pros as optimistic concurrency control
    ConsRisk of deadlocks occurring, not scalable especially if transactions are locked for too longPoor performance during heavy data contention (many simultaneous updates on the same resource)Poor performance during heavy data contention, not supported by all databases