Developed with pleasure!
Campsite Booking API
A RESTful web service that manages campsite bookings.
Read this blog post to learn more details.
Technical Task
Booking Constraints
- The campsite can be reserved for max 3 days.
- The campsite can be reserved minimum 1 day(s) ahead of arrival and up to 1 month in advance.
- Reservations can be cancelled anytime.
- For sake of simplicity assume the check-in & check-out time is 12:00 AM.
System Requirements
- The users will need to find out when the campsite is available. So the system should expose an API to provide information of the availability of the campsite for a given date range with the default being 1 month.
- Provide an end point for reserving the campsite. The user will provide his/her email & full name at the time of reserving the campsite along with intended arrival date and departure date. Return a unique booking identifier back to the caller if the reservation is successful.
- The unique booking identifier can be used to modify or cancel the reservation later on. Provide appropriate end point(s) to allow modification/cancellation of an existing reservation.
- Due to the popularity of the campsite, there is a high likelihood of multiple users attempting to reserve the campsite for the same/overlapping date(s). Demonstrate with appropriate test cases that the system can gracefully handle concurrent requests to reserve the campsite.
- Provide appropriate error messages to the caller to indicate the error cases.
- The system should be able to handle large volume of requests for getting the campsite availability.
- There are no restrictions on how reservations are stored as long as system constraints are not violated.
Up & Running
Maven
$ git clone https://github.com/igor-baiborodine/campsite-booking.git
$ cd campsite-booking
$ mvn spring-boot:run -Dspring-boot.run.profiles=h2
The Swagger UI is available at http://localhost:8080/swagger-ui.html
.
Executable JAR
$ git clone https://github.com/igor-baiborodine/campsite-booking.git
$ cd campsite-booking
$ mvn package -DskipTests
$ java -jar -Dspring.profiles.active=h2 target/campsite-booking-<version>.jar
The Swagger UI is available at http://localhost:8080/swagger-ui.html
.
Docker
$ git clone https://github.com/igor-baiborodine/campsite-booking.git
$ cd campsite-booking
$ docker build --rm -t campsite-booking .
$ docker run -e "SPRING_PROFILES_ACTIVE=h2" --name campsite-booking -d campsite-booking
$ docker logs -f campsite-booking
The Swagger UI is available at http://container-ip:8080/swagger-ui.html
. To get the container IP address, execute the following command:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' campsite-booking
Via the host machine on port 80:
$ docker run -e "SPRING_PROFILES_ACTIVE=h2" --name campsite-booking -p 80:8080 -d campsite-booking
The Swagger UI is available at http://localhost:80/swagger-ui.html
or http://host-ip:80/swagger-ui.html
.
... or with an image from Docker Hub:
$ docker run -e "SPRING_PROFILES_ACTIVE=h2" --name campsite-booking -p 80:8080 -d ibaiborodine/campsite-booking
... or with docker-compose:
$ docker-compose up -d
Continuous Integration
Continuous integration is implemented using GitHub Actions, and it includes the Build on Pull Request
, Build Master Branch
, and Perform Release
workflows:
Build on Pull Request
This workflow is executed automatically on any pull request and consists of the Unit & Integrations Tests
and SonarCloud Scan
job:
Build Master Branch
This workflow is executed automatically on any commit to the master
branch and consists of the SonarCloud Scan
and Snapshot Publishing
jobs:
Perform Release
This workflow is executed manually and consists of the Maven Release
and Docker Image
jobs:
The Release Version
parameter value should be provided before executing this workflow:
Tests
Maven
- Run only unit tests:
$ mvn clean test
- Run only integration tests:
$ mvn clean failsafe:integration-test
- Run unit and integration tests:
$ mvn clean verify
- Run SonarCloud analysis, including test coverage, code smells, vulnerabilities, etc.:
$ mvn clean verfify sonar:sonar -Dsonar.login=<SONAR_TOKEN> -Pcoverage
Swagger UI
The API can be tested via the Swagger UI:
For example, to add a new booking, expand the POST
operation. Then click on the Try it out
, add the payload below to the Request Body
text area, and click on the Execute
:
{
"uuid": "8db6b1f4-27ba-11eb-adc1-0242ac120001",
"email": "john.smith.1@email.com",
"fullName": "John Smith 1",
"startDate": "2021-01-21",
"endDate": "2021-01-23"
}
If the operation is successful, you will get the following response:
H2 Console
When running with the h2
profile, the H2 console is available at http://localhost:8080/h2-console
.
Fill the login form as follows and click on Connect:
- Saved Settings: Generic H2 (Embedded)
- Setting Name: Generic H2 (Embedded)
- Driver class: org.h2.Driver
- JDBC URL: jdbc:h2:mem:campsite;MODE=MySQL
- User Name: sa
- Password:
Concurrent Bookings Creation
Note: should be executed with the mysql
active profile
Start an instance of Campsite Booking API and execute the concurrent-bookings-test.sh script to simulate concurrent booking creation for the same booking dates:
$ docker-compose up -d
$ test/concurrent-bookings-test.sh 2021-02-01 2021-02-04 http:/localhost:80
The response should be as follows after formatting, i.e., only one booking was created:
{
"id":2,
"version":0,
"uuid":"ea2e2f8f-749d-4497-b0ec-0da4bf437800",
"email":"john.smith.3@email.com",
"fullName":"John Smith 3",
"startDate":"2021-02-01",
"endDate":"2021-02-04",
"active":true,
"_links":{
"self":{
"href":"http://localhost/v1/booking/ea2e2f8f-749d-4497-b0ec-0da4bf437800"
}
}
}
{
"status":"BAD_REQUEST",
"timestamp":"2021-01-28T02:52:19.10936",
"message":"No vacant dates available from 2021-02-01 to 2021-02-04"
}
{
"status":"BAD_REQUEST",
"timestamp":"2021-01-28T02:52:19.210229",
"message":"No vacant dates available from 2021-02-01 to 2021-02-04"
}
Basic Load Testing
Basic load testing for retrieving vacant dates can be performed with the ApacheBench by executing the following command:
$ docker-compose up -d
$ ab -n 10000 -c 100 -k http://localhost:80/v1/booking/vacant-dates
- -n 10000 is the number of requests to make
- -c 100 is the number of concurrent requests to make at a time
- -k sends the KeepAlive header, which asks the web server to not shut down the connection after each request is done, but to instead keep reusing it
Result:
Benchmarking localhost (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requests
Server Software:
Server Hostname: localhost
Server Port: 80
Document Path: /v1/booking/vacant-dates
Document Length: 365 bytes
Concurrency Level: 100
Time taken for tests: 4.250 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 4700000 bytes
HTML transferred: 3650000 bytes
Requests per second: 2352.71 [#/sec] (mean)
Time per request: 42.504 [ms] (mean)
Time per request: 0.425 [ms] (mean, across all concurrent requests)
Transfer rate: 1079.86 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.6 0 9
Processing: 3 42 33.4 32 339
Waiting: 2 41 33.0 32 338
Total: 3 42 33.5 32 339
Percentage of the requests served within a certain time (ms)
50% 32
66% 50
75% 56
80% 59
90% 85
95% 107
98% 144
99% 156
100% 339 (longest request)