This project was undertaken to participate in the HACK-IT! at OCBC challenge.
Live Demo: https://hackit.manmohanjit.net/
I have never touched Java until the project brief was released around mid-November. However, I do have many years of experience in PHP (Laravel), Javascript (React/React Native/Node.js), and Cloud Services.
- Explore and learn Java
- Explore and learn Spring Boot and its ecosystem
- Learn common design patterns in Spring Boot
- Started very slow learning Java syntax, but after griding for 1-2 days I managed to figure out the syntax
- I have a newly found love of working with strongly typed languages
- Spring Boot is very powerful however, it has a steep initial learning curve, but after some time it becomes progressively easier to dive into deeper topics
- Spring Boot requires a lot of boilerplate code...
- However, luckily there are many libraries that help reduce it (mapstruct, lombok, etc)
- Spring Boot MVC as API server
- Create React App (CRA) as front-end application
- Support multiple movies, shows, categories, halls
- Abstraction of data layer provides flexibility to switch data sources
- Database transactions and locks to handle concurrency and data consistency
- Third-party SMTP service (SendGrid) to send out emails (non-blocking)
- Deployed on an NGINX server block to serve as a reverse proxy so everything is hosted on same domain
- CloudFlare provides HTTPs for the domain
- CORS is enabled for development and testing purposes
- âś… Create a web application that allows users to book a seat in a movie theatre - no authentication required.
- âś… This assignment aims to create a fully functional web application with Backend in Java and Front-end in related technologies such as ReactJS.
- âś… Alternatively, you could use a Java MVC framework for creating both the Backend and Frontend.
- âś… Display all the seats in a theatre and allow users to book them.
- âś… If a user clicks a seat that another user booked, they should get an error. You must handle the concurrency scenarios and avoid data inconsistency.
- âś… If a seat is available, the user should be asked for their details like name, email ID, etc. and email them with a confirmation. You do not need to gather the payment details.
- âś… The solution should have a single web page with the seats displayed in a grid (you could start by a smaller number of seats, maybe 20-30)
- âś… Hosting a live solution is encouraged and will increase the chance of a higher score for this assignment.
- âś… Test coverage is essential.
- âś… Complete installation instructions for your application must be available via README.md.
- âś… Please create a new Git repo where you will upload the relevant project files. Please properly document your code.
- Apache Maven 3.8.4+
- Java Version 17+
- H2 (built-in) or MySQL (external)
- Node 16.5.0
- SMTP mail credentials (optional)
Available at http://localhost:8080
cd hack-it-2021
mvn compile
mvn package
java -jar target/booking-0.0.1-SNAPSHOT.jarNote: You might want to adjust src/main/resources/application.properties so that local email works, or if you want to use MySQL as the database driver.
If you are using Linux, you can use systemctl to create a long-running service.
/lib/systemd/system/hackit.service
[Unit]
Description=hackit webserver Daemon
[Service]
ExecStart=/usr/bin/java -jar /home/ubuntu/hack-it-2021/target/booking-0.0.1-SNAPSHOT.jar
User=ubuntu
[Install]
WantedBy=multi-user.target
Available at http://localhost:3000
cd hack-it-2021
cd frontend
npm install
npm run startYou can run cd frontend && npm run build to generate static files and production ready assets which will be available at: ./frontend/build
You can set-up a reverse proxy via NGINX to serve static React files and the Java Web API on the same domain using the following config file:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm;
server_name _;
location / {
try_files $uri /index.html;
}
location /api {
proxy_pass http://localhost:8080/api;
}
}The hall module contains Hall and Seat entities. Each hall has many seats. This application can have multiple halls available so concurrent shows can be sold at the same time in different halls.
Each movie will have multiple showtime, that can be linked to different halls.
Each movie will also have multiple ticket types and prices.
The inventory module links hall seats, showtimes and categories to keep track of available tickets.
Orders will interact with multiple module repositories to validate, store and list orders.
The front-end is built on CRA, and stored in the same repository to simplify things. The React app itself, is fairly simple.
- React Router DOM - Routing library
- Axios - data fetching and error handling
- React Query - Server State management
- Formik - Form state management
- Bootstrap - UI styling
- Panzoom - SVG zoom-in/out
- React QR Code - Generate QR code SVG on the fly
/- homepage/movies/{movieId}- single movie to see a summary of showtimes/purchase/{showId}- single show to see available seats/orders/{orderId}- order page to complete and view order
- Direct DOM manipulation on seat map SVG to handle inventory state (Available, Sold, Reserved)
Each module is stored in its own directory and separated from others. There are a total of 4 primary modules and 5 secondary modules.
- Entity (model data from database)
- Repository (retrieve and interact with database)
- Service (perform business logic)
- DataTransforObjects (carry data from requests and also from responses)
- Mapper (mapstruct class to simplify conversion of Entity -> DTO and DTO -> Entity)
- Controller (handle web requests)
Get a list of showing movies
[
{
"id": 1,
"title": "The tales of Paltisaur",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
]Find a single movie via movieID
{
"id": 1,
"title": "The tales of Paltisaur",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}Get a list of showtimes for a movie via movieID
[
{
"id": 1,
"startsAt": "2021-12-01T16:00:00.365633",
"endsAt": "2021-12-01T18:00:00.365677",
"hallId": 2,
"movieId": 1
},
{
"id": 2,
"startsAt": "2021-12-01T18:00:00.36569",
"endsAt": "2021-12-01T20:00:00.365694",
"hallId": 1,
"movieId": 1
},
{
"id": 3,
"startsAt": "2021-12-01T20:00:00.365699",
"endsAt": "2021-12-01T22:00:00.365702",
"hallId": 2,
"movieId": 1
}
]Get a list of categories for a showtime via movieID
[
{
"id": 1,
"label": "Standard",
"price": 2500,
"colour": "red"
},
{
"id": 2,
"label": "Wheelchair",
"price": 1500,
"colour": "cyan"
},
{
"id": 3,
"label": "VIP",
"price": 5000,
"colour": "orange"
}
]Find a single showtime via showID
{
"id": 1,
"startsAt": "2021-12-01T16:00:00.365633",
"endsAt": "2021-12-01T18:00:00.365677",
"hallId": 2,
"movieId": 1
}Find available inventory (seat and category) for a show via showID
[
{
"id": 1,
"category": {
"id": 1,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 167,
"label": "I-14"
},
"status": "AVAILABLE",
"enabled": true
},
{
"id": 2,
"category": {
"id": 1,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 168,
"label": "I-16"
},
"status": "AVAILABLE",
"enabled": true
},
...
]Find a hall via hallID
{
"id": 1,
"title": "Cinema 1",
"seatMap": "<svg>....</svg>"
}Get a list of seats for a hall via hallID
[
{
"id": 1,
"label": "B-7"
},
{
"id": 2,
"label": "C-7"
},
...
]Create a new order. Requires showId and an array of inventory item IDs.
# Example Request
showId: 3
items[]: 227
items[]: 170
{
"id": "4a7220dc-486c-4373-aa3a-a2e272a809a7",
"createdAt": "2021-11-30T22:14:38.968428",
"updatedAt": "2021-11-30T22:14:38.968456",
"status": "INITIAL",
"name": null,
"email": null,
"showId": 3,
"items": [
{
"id": 227,
"category": {
"id": 2,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 74,
"label": "K-5"
}
},
...
]
}Update order via orderId with details (name, email) to transition order state to PENDING.
# Example Request
name: Manmohanjit Singh
email: test@example.com
{
"id": "4a7220dc-486c-4373-aa3a-a2e272a809a7",
"createdAt": "2021-11-30T22:14:38.968428",
"updatedAt": "2021-11-30T22:17:41.833522",
"status": "PENDING",
"name": "Manmohanjit Singh",
"email": "test@example.com",
"showId": 3,
"items": [
{
"id": 227,
"category": {
"id": 2,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 74,
"label": "K-5"
}
},
...
]
}Expires an order pragmatically via orderId
{
"id": "4a7220dc-486c-4373-aa3a-a2e272a809a7",
"createdAt": "2021-11-30T22:14:38.968428",
"updatedAt": "2021-11-30T22:17:41.833522",
"status": "EXPIRED",
"name": "Manmohanjit Singh",
"email": "test@example.com",
"showId": 3,
"items": [
{
"id": 227,
"category": {
"id": 2,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 74,
"label": "K-5"
}
},
...
]
}Completes an order pragmatically via orderId
{
"id": "4a7220dc-486c-4373-aa3a-a2e272a809a7",
"createdAt": "2021-11-30T22:14:38.968428",
"updatedAt": "2021-11-30T22:17:41.833522",
"status": "COMPLETED",
"name": "Manmohanjit Singh",
"email": "test@example.com",
"showId": 3,
"items": [
{
"id": 227,
"category": {
"id": 2,
"label": "Standard",
"price": 2500,
"colour": "red"
},
"seat": {
"id": 74,
"label": "K-5"
}
},
...
]
}- Tests for all order endpoints
- All routes allow CORS
- Concurrency is handled via database locks
- Emails are non-blocking, and performed asynchronously via SMTP
- Lombok and Mapstruct are used to reduce boilerplate code
- commandLineRunner in
BookingConfig.javais used to seed data - Some exceptions are handled and transformed into standardized responses in the
errorsmodule
Redis being an in-memory store would be suitable to handle inventory management and tracking. It can be used to handle locks as well as a caching solution. With Redis, we can help speed up requests as well as reduce the load on the database servers.
As the app grows, a more robust queue pattern to handle heavy requests can be implemented to avoid the possibility of server/database being overworked.
Being able to design an application for HA will ensure that we can reduce the risk of downtime. The benefits from HA will also allow us to scale better horizontally and handle higher amounts of traffic or traffic surges.
Using Docker, it will be easier to manage deployment and infrastructure as the team expands.
