In this post, we try to present a general template for reservation system design. Reservation system is a general concept and it has many flavors. Here is a list of common examples:
- Hotel booking system
- Flight booking system
- Restaurant reservation system
- Parking lot
As always, a system is defined by its components and the associated behavior. We will start with defining components used in such system and present general data models. Then we will briefly talk about some APIs commonly used in reservation system. We will finish this post by discussing a little bit the architecture design details.
There are three main categories in a reservation system
This is quite obvious. We need user to interact with the system. There are two types of users in a reservation system:
- resource consumer
- resource provider
For example, in a restaurant reservation system, customers who book tables are the resource consumers and restaurant owners are the resource providers. Resource providers are relatively "static" compared to resource consumers; they interact less with the reservation system in most cases.
The data for users is straightforward:
Group and Resource
Resource is an entity that can provide a service to customer. For example, a table in a restaurant is a resource; a seat in an airplane is also a resource.
Most of the time, resources are grouped together. For example, a restaurant has many tables and similarly an airplane has many seats. In these cases, the restaurant and the airplane are groups (of resources).
The data model is given as follows:
Group and resource are provided by resource providers and only resource providers can modify them.
Reservation and Reserved Resource
In a reservation record, we should have the information of the user who makes the reservation and the reserved resource. It should also have a time component, which is period during which the resource is reserved. It can happen that a reservation uses multiple resources so we will have a separate table for reserved resource. This provides additional flexibility; on the other hand, when we query the resource availability we need to merge the Reservation and ReservedResource tables together.
Find Available Resource
The inputs in the query are the time range and some conditions, such as location, type of resource, etc. We will first query the Group table because it contains more meta data of resources. The outcome of this step is a list of groups that meet the requirements.
Then we can collect the following information
- The resources that the groups have.
- The reserved resource in these groups.
And the difference is the available resources. The API is given as follows:
List<Resource> findAvailableResource(DateTime startTime, DateTime endTime, Condition conditions);
Making reservation involves updating the
Reservation table and the
ReservedResource table. Due to the asynchronous nature of the system, before we reserve a resource we need to verify again the resource in question is still available.
Here is the API:
Reservation reserveResource(Resource resource);
Users can cancel reservation if they no longer want to use the resource. To cancel a reservation, we just need to update the
There are different ways to represent a cancelled reservation. We could delete the record from the table or add more fields to the data model. For example, we could add a
state field to
Reservation record. When a new reservation is created, the state field is set to effective; when a reservation is cancelled, the state is set to cancelled.
Here is the API:
boolean cancelReservation(Reservation reservation);
Releasing resource is similar to canceling reservation. The difference is rather semantic. One use case of
releaseResource API is when a car leaves a parking lot, the resource is released and the spot becomes available again.
boolean releaseResource(Reservation reservation);
Architecture and Other Details
As mentioned earlier, there are two types of users:
- Resource consumer
- Resource provider
There are also two main types of activities:
- Resource management
- Reservation management
We could have two separate services to handle these two tasks because we want to follow the divide-and-conquer paradigm and limit the blast radius. For example, we don't want to be in a position where a bug in resource management code has impact on reservation activities.
Caches and Data Partition
At some point in the process, we need to manipulate the database and the data on the disk, which is a very slow operation. Following standard optimization approach, we need to have caches and we need to partition the data.
In general, a reservation system needs to support concurrent requests, i.e. different users can make reservation at the same time. For they may reserve the same resource, most of reservation systems requires strong consistency model. Eventual consistency is not appropriate because it means we may have two customers reserving the same resource for the same period. If we use lock mechanism to achieve stronger consistency, we need to be careful about how we lock a table. For example, for a restaurant booking system, we may have many restaurants stored in the same table and we should avoid locking the entire table, which impacts many restaurants at the same time, only to serve one single user who is making a reservation to a particular restaurant. More specifically, row-low is much preferred.
Data Retention Policy
Data storage is very cheap nowadays so it's not a problem anymore. However, we cannot keep all the historical data otherwise as database tables grow larger, it will take longer to execute the query and we will see a gradual degradation of runtime performance.
We should just keep the needed data in our database and move historical data to some other data archive system such as S3 Glacier.
----- END -----
©2019 - 2022 all rights reserved