- run
docker-compose up
There are three main requirements to consider:
- Restaurants should be able to define how many reservations of a specific party size to take every 15 minutes.
- Restaurants should be able to book a reservation on behalf of guests.
- Reservations should never exceed inventory.
- Inventory should be configurable via a range.
- Should the application handle multiple restaurants? (Not a hard requirement)
- Is concurrency when booking a reservation an issue? (No)
- Should restaurants be allowed to update / delete inventory? (Not a hard requirement)
- Should a party of 1 be allowed to book a table for 6? (Yes)
inventories / InventoryModel
- id serial
- startTime time
- endTime time
- maxSize integer
- maxParties integer
- createdAt
reservations / ReservationModel
- id serial
- name text
- email text
- size integer
- date date
- time time // duplicated data
- createdAt
Duplicating the time column in the reservations table introduces the possibility of the column desyncing from the related inventory row. While this is not an issue since there is no requirement to update existing inventories, it can be handled by an application enforced constrait where inventories with existing reservations cannot update the time column. From a product pov, this also makes sense because restaurants wouldn't want to change reservation times without reaching out to the customer first. Data duplication also takes additional space, but adding a single 8 byte column shouldn't be an issue.
The benefits gained are as follows:
- Cleaner REST API where the create reservation route can respond with a ReservationModel.
- Eliminates the need to join on inventories to get time, speeding queries where the search field is time.
This could very much be a premature optimization, but the cost to reverse this potential tech debt is quite low. See the Postgres docs on the performance cost of dropping a database column.
Error responses will include the error (string) and code (integer) properties. The code property is in addition to the HTTP status code and is unique for each type of error. For ex:
{
message: 'Invalid Time',
code: 1000,
}
POST /inventories
{
startTime: ISO-8601 time string, // Format: HH:MM API expects UTC timezone
endTime: ISO-8601 time string, // Format: HH:MM API expects UTC timezone
maxSize: number, // max number of guests for a reservation
maxParties: number, // max number of reservations allowed to be booked during this timeslot and party size
}
200
- Successful creation of inventory
{
...InventoryModel
}
400
Status Code Errors
{ code: 500, error: `'time' must be a ISO-8601 date string formatted as HH:MM` }
{ code: 1000, error: 'Invalid time, reservations can only be accepted in 15 minute intervals' }
{ code: 1001, error: 'Inventory already exists for the specified time and party size' }
{ code: 1002, error: `'time' and 'maxSize' must be provided` }
{ code: 1003, error: `'maxSize' must be a number` }
{ code: 1004, error: `'maxParties' must be a number` }
POST /reservations
{
name: string, // guest name
email: string, // guest email
size: number, // number in party
date: string, // Format: YYYY-MM-DD
time: ISO-8601 time string, // Format: HH:MM API expects UTC timezone
}
200
- Successful reservation
{
...ReservationModel
}
400
Status Code Errors
{ code: 1100, error: 'Inventory not configured for that time' }
{ code: 1101, error: 'No more reservations available for that time' }
Since we don't have to worry about concurrency this is quite straight forward. The following pseudocode describes how we can enforce this constraint:
const inventory = await query('select id, max_size, max_parties from inventories where size <= max_size and time = time;');
const reservationCount = await query('select count(id) from reservations where date = date and time = time;');
if (reservationCount >= inventory.maxSize) {
throw HTTPError(400, 'Inventory unavailable');
}
const reservation = await query('insert into reservations (date, time) values (date, time)');
TODO