This repo contains a Flask API for seating groups of attendees to a given rank
Before running the application you will need to create a .env
file at the root of the project with the following values:
QUART_APP=src
HOST=0.0.0.0
PORT=8080
WORKERS=4
LOG_LEVEL=debug
MONGO_HOST=localhost
MONGO_PORT=27017
MONGO_NAME=theater_seating_db
MONGO_CONNECT_ON_STARTUP=False
To run the basic server, you'll need to install a few requirements. First, run:
virtualenv venv
source venv/bin/activate
Then:
pip install -r requirements/common.txt
This will install only the dependencies required to run the server. To boot up the default server, you can run:
export QUART_APP=src && quart run
Alternatively the following make
commands will perform the setup for you:
make venv
make install
make start
This will start a Hypercorn ASGI server that wraps the Flask app
defined in src/app.py
.
You should now be able to send:
curl localhost:8080/health
And receive the response OK
and status code 200
. This also has the added benefit of spinning up/checking the connection to the DB if no requests have been made yet.
This is the preferred way of running this application.
You'll need Docker installed to run this project with Docker. To build a containerized version of the API, run:
docker compose up --build -d
You should see the db image downloaded, the server boot up, and should be accessible as before.
All the below requests can also be activated by Swagger, pasting in the bodies from the cURL commands will work fine. Start the server and visit the Swagger URL.
As this codebase interacts with a MongoDB database there is an initial step required for a quick test run of the code. However this step is easy with the make
command:
make init-db
If you have your docker container running then the above command will run the initdb
command within Quart to set up for the test run. This creates the 24 seats in 3 rows from the problem statement. Note that if you cloned the repo under a different name then this command will not work, and instead you will need to run the following command:
docker exec <folder_name>-theater_seating-1 quart initdb
Next, we need to find the ObjectID of the venue that was created in the DB for us:
curl localhost:8080/venue
This will display an oid
that you will need to copy. Next we will use this oid
to kick off the seating/ticketing algorithm:
curl -X POST -H "Content-Type: application/json" -d '{"groups":[1,3,4,4,5,1,2,4],"rank":"1","venue_id":"<venue_id_here>"}' localhost:8080/seats/order
You should see the following displayed on screen:
[1, 2, 2, 2, 3, 3, 3, 3]
[8, 8, 8, 8, 4, 4, 4, 4]
[5, 5, 5, 5, 5, 6, 7, 7]
Finally, you can take a user's group ID and get a list of their tickets from the wallet endpoint:
curl localhost:8080/wallet/2
The application also supports seating users with various preferences. Which can be tested with:
curl -X POST -H "Content-Type: application/json" -d '{"groups":[1,3,4,4,5,1,2,4],"rank":"1","venue_id":"61c95f272ae66f3472c7f6e4", "prefs":{"2":"aisle"}}' localhost:8080/seats/order
All of the above curls can be found in the .curls
file in this repo.
Beyond this point I will discuss various design decisions that were made as the application was being built.
To solve the non-blocking ask for this project I've written the original service in Flask, then migrated to Quart based on the Flask Docs recommendation.
For this project I selected MongoDB. For a given event the number of tickets could average 20,000+, which is a relatively small amount of documents. Even at hundreds of those events a day we would barely scratch the performance on Mongo and with NoSQL we get the benefit of flexible collection/table structure.
To solve the problem in the problem statement we will need several data structures stored across several collections.
The data structure to represent an event venue will be broken up and stored in the venue-layout
collection within mongoDB. The top level document will contain the sections and the section metadata. This collection is the main reason for MongoDB, as we are free to define the section names at will. An example layout is below:
{
_id: '',
venue_name: 'name',
main_hall: [{
floor: 0,
curved: False,
total_seats: 10,
layout: [] # layout is empty within the database, populated in the api
... // additional params
}, ...],
balcony1:[{...}],
balcony2:[{...}],
...
}
This document would be pulled by ID when the user clicks "buy tickets" on a given site. It would enable display of the entire layout of the venue as an image, and clicking on a various section would query a separate collection for the documents below.
In a real-life implementation you would need a role-secured endpoint to POST new venue data to. There could be an admin panel that the sales department uses or shares with the venue to define their venue.
The next step is to have a collection that is relatively static representing each possible seat within a given venue. The documents are returned via the venue ID and section and an accumulator places them within the above data structure. Example below:
{
_id: '',
venue_id: '',
section: 'main_hall',
rank: '1', # refers to value/placement of seat selection, e.g. rank 1 is better seating than 3.
seat_number: '1',
row: '1', # String, some venues may use letters for rows
aisle_seat: True
}
These documents would be pulled as the user drills into a given section in the venue while going through the ticket purchasing process. In this site, a user can only view one section at a time to calls to the DB would be minimal. However, to ensure speed, we can apply an ascending index on the row
field to return the seats sorted by row. If desired, a compound sort can sort first by row, then by seat.
Once we've pulled the layout from the DB into the app to send to the user, we want to allow the user to purchase and thereby "lock" a seat to that user. The process of locking is not something that is an ask of this project so we will only worry about issuing tickets and not checking if a ticket can be issued. Example document:
{
_id: '',
user_id: '',
event_id: '',
venue_id: '',
group_size: '',
rank: '',
preference: '',
created_at: '',
redeemed_at: ''
}
When a user clicks into their wallet on the site, a request is made to the DB to return tickets based on user_id
. Additionally a date range could be placed on the query if the user has many tickets.
To run the unit tests, activate the virtual environment and then run the below commands:
pip install -r requirements/develop.txt
pytest