System requirements


Functional:

  • Users can create accounts to manage their parking reservations
  • Users can search for parking lots in any area they're looking for
  • Users can book a parking lot
  • Users can pay for the parking lot
  • On Arrival user can use the booked parking lot



Non-Functional:

  • Scalability: System needs to be scalable for a lot of users using it, as the data storage and also the concurrent transactions are scaling
  • Reliable / Durable: User expects to have the booking placed reliable, so on arrival he can use the parking lot without any issues




Capacity estimation

Our monthly new users are estimated as 1000 with 2 bookings per month on average.

This leads to 2000 bookings per months.

We can assume that there will be roughly 10 concurrent bookings for the same parking lot at a peek.




API design

User API

This API solely focuses on the User informations and can be designed with the following endpoints:

  • GET /users/:id -> retrieves information for a single user
  • POST /users -> stores a new user (this will be used when a user creates a new account)
  • DELETE /users/:id -> user decides to delete his account
  • PATCH /users/:id -> user makes changes on his user account


Booking API

This API is responsible for the actual booking operations and can be designed with the following endpoints:

  • GET /bookings/:id -> retrieves informations for a single booking
  • POST /bookings -> creates a new booking
  • DELETE /bookings/:id -> deletes an existing booking
  • PATCH /bookings/:id -> changes an existing booking


Database design

Let's see the Data model first. Considering the two APIs from the previous sections, there are 2 main entities:

  • User
  • Booking


Let's design User first. It should consists of the following information:

  • id
  • name
  • address
  • created_at


Let's now design a Booking. We need to consider that a booking is unique and only belongs to a single user. A user can have multiple bookings. So we have a one-to-many relationship here. The Data model should consist of the following information:

  • id
  • address
  • user_id
  • status
  • created_at
  • updated_at


For the sake of simplicity I'm putting the location of the parking lot as an address field. usually there could be another entity, called parkingspace for example, which would have more information about the type of the booking, the location and such. For now we want to keep it simple. The status indicates the current status such as: open, cancelled, paid, complete.



High-level design

flowchart TD

B["User"];

n1["Booking Service"];

n2[("Database")];

B --> n1;

n1 --> n2;

n2 --> n1;

n1 --> B;








Request flows

Here I'll explain the high level designs request flow. In its essence it's super simple. The User just calls a backend via a client (e.g. web interface) to both do user profile based operations and also booking operations. The backend handles all of the related operations and also has a connection to the database to write and read necessary data.





Detailed component design


As the high level design is not enough to accomodate all the needs we have (such as payment), we'll dig deeper into the design of the system in this section.


flowchart TD

n3["API Gateway"];

n4(("User"));

n6["Booking Service"];

n7["Payment Service"];

n8[("Database")];

n4 --> n3;

n3 --> n6;

n3 --> n7;

n6 --> n8;

n7 --> n6;






Trade offs/Tech choices

The Backend is separated now 2 services, one is responsible for the booking and one for the payment. Another option would have been that the booking and user service could also have been separated. But there's an important trade off to make. When having those two separated, it would add complexity in terms of keeping the user database and the booking database in sync, so the reference between a user and a booking can be established. Having them in one service with a single database, allows us to use the consistency mechanisms of the database itself.

Also the user profile is built to manage the bookings, so there's a reasonable cohesion between the two responsibiltiies of this service.


Considering the estimations of the scale, it should be fairly resonable to have a single database node, as it should be sufficient to have a single node. We'd also have an advantage for concurrent writes in this scenario, as we only have a single replica of the DB. However the application logic has still to be involved in locking the database for reservations on the same parking lot.


Furthermore a caching layer is introduced on a user basis. it would store a user id as key and all the booking informations of the user in it's key value store.


The Payment Service takes care of all payments and the user is redirected via the gateway to the payment service. Once a payment is done (no matter if it was successfull or not) it calls the Booking Service again so the User journey can continue.

All communication appears synchronous here. It's important to make a trade off analysis for the synchronous communication, compared to the asynchronous one. For sure, the synchronous communication will lead to longer response times. But considering the user journey, it's expected to wait during a payment state to get feedback. So the trade off of introducing more complexity for asynchronous and thus faster communications, doesn't really apply to the user journey.




Failure scenarios/bottlenecks

I'd still be wary about concurrent bookings on the same parking lot. Even if it's not that important for our current scale, it could be dangerous to sell the same parking lot to X (greater than 1) persons for the same time.

This needs to be thought thoroughly.




Future improvements

The parking entity can be advanced (introduced), to have an entity reference from the booking to the parking. This would also enable to store informations such as capacity, type and such. This would also allow the concurrent parking lot booking issue to be addressed by the application layer much more easier, as it could handle with a clear entity that stores information needed.