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.
User API
This API solely focuses on the User informations and can be designed with the following endpoints:
Booking API
This API is responsible for the actual booking operations and can be designed with the following endpoints:
Let's see the Data model first. Considering the two APIs from the previous sections, there are 2 main entities:
Let's design User first. It should consists of the following information:
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:
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.
flowchart TD
B["User"];
n1["Booking Service"];
n2[("Database")];
B --> n1;
n1 --> n2;
n2 --> n1;
n1 --> B;
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.
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;
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.
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.
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.