Hotel Reservation System Implementation with RavenDB

by Shahar Hikri

Introduction

Efficient hotel reservation management is a crucial challenge, especially in luxury hotels where guests can place bids for rooms. The system must be fast, reliable, and capable of handling large amounts of data in real-time. 

While there are many systems available on the market that handle basic reservation management, using RavenDB sets this system apart by offering advanced search capabilities and real-time data management with custom indexes. 

RavenDB ensures high performance in handling complex and large datasets, which is particularly important for systems like this one, which requires quick access not only to room reservations but also to bidding offers and demand trends analysis.

In this article, we will explore implementing a hotel reservation system using RavenDB, leveraging fanout indexes and map-reduce queries to optimize performance and usability. Additionally, we will analyze specific queries and their role in the system. As a reference, we’ll use the RavenWorkshop repository (GitHub).

The RavenWorkshop Hotel Reservation System efficiently manages room reservations and incorporates a bidding system. 

It ensures fast availability checks and demand trend analysis using RavenDB’s advanced indexing. Fanout indexes optimize reservation queries, while map-reduce indexes track popular rooms and peak bidding days. 

Through custom queries, hotel managers can gain valuable insights into pricing strategies and seasonal demand, enabling data-driven decisions and improving operational efficiency. 🚀 

Business Scenario

Our system supports:

  • Customers: Checking room availability, placing bids, and making reservations.
  • Hotel Owners: Analyzing demand trends, optimizing room pricing, and planning maintenance schedules.
  • Operational Staff: Managing room cleaning and scheduling based on demand patterns.

The following sections describe how to implement key queries and indexes using RavenDB.


1. Data Model Overview

Before diving into queries, it’s essential to understand the data model used in the system. 

The core entities are Bid, Reservation, and Room – these classes define the structure of the hotel reservation system.

Room Class

Represents a room at the hotel.

internal class Room
{
    public string Id { get; set; } // Unique identifier for the room (can be the room number)
}

Bid Class

Each bid represents an offer made by a customer for a specific room.

internal class Bid
{
    public string Id { get; set; } // Unique identifier for the bid

    public string Room { get; set; } // The room the bid applies to

    public bool Vip { get; set; } // Indicates whether the bid is from a VIP customer

    public DateOnly Start { get; set; } // The start date of the bid

    public DateOnly End { get; set; } // The end date of the bid 
}

Reservation Class

internal class Reservation
{
    public string Id { get; set; } // Unique reservation ID

    public string Room { get; set; } // The reserved room

    public DateOnly Start { get; set; } // Check-in date of reservation

    public DateOnly End { get; set; } // Check-out date of reservation
}

Reservations ensure that a room is blocked for the given period.

Important for availability checks and avoiding overbookings.

Used in fanout indexing to store individual reservation days.

2. Indexes Overview

RavenDB offers advanced solutions for performance management, especially when dealing with large datasets and complex queries. 

In a hotel reservation system, there are several performance challenges, such as frequent data changes (reservations, bids) and storing frequently updated data.

The advantage of RavenDB in such a system lies in its use of indexes that simplify searches and perform dynamic calculations, such as Map-Reduce and Fanout indexes, which reduce the need for automatic data processing.

However, it is important to be mindful of the challenges when working with large and complex data. Therefore, careful design of indexes is crucial for optimizing query performance. 

Using RavenDB not only improves search performance but also prevents issues like data duplication and unnecessary document loading, ensuring the system remains efficient and responsive even under heavy load.

To further optimize query performance and ensure efficient data retrieval, RavenDB leverages indexes to precompute and structure data. These indexes not only enhance query speed but also enable more complex, real-time analytics. 

In the context of the hotel reservation system, we will examine several crucial indexes: ‘Bids_ByDay’, ‘Bids_ByRoom’, ‘Bids_ByDay_ByRoom’, and ‘Reservations_ByDay’. We will also demonstrate how these indexes facilitate searches and queries.

Bids_ByDay Index

This index is a “Fanout” type (can read more about that here), designed to aggregate bidding activity by day.

For each day it aggregates the total number of bids, the number of VIP bids, and which rooms received bids on that day.

Each bid is mapped to every day it covers from its start to its end date. For instance, if a bid spans from January 1st to January 3rd, the index will create entries for January 1st, 2nd, and 3rd.

In the Map function, each day a bid is active is calculated by iterating through the range from the start to the end day of the bid, ensuring each day is accounted for separately. The index then assigns the total bid count to 1 and if placed by a VIP customer, a VIP bid count to 1 for each of these days. Also associating the bid with the room that received the bid.

The reduce groups the results by day, summing the total bids, VIP bids, and collecting distinct rooms for each day. 

This structure exemplifies a fanout index: each document generates multiple index entries—one for each day covered, demonstrating the fanout principle. The reduction phase then aggregates these entries based on one or more specified fields.

For additional information on the “Fanout Index” you can look here.

This lets us the ability to run specific queries that identify the most active bidding days, track which rooms received bids on those days, and analyze the overall bidding activity, which can be useful for optimizing room pricing and availability in the hotel management system.

    internal class Bids_ByDay : AbstractIndexCreationTask<Bid, Bids_ByDay.Entry>
    {
        internal class Entry
        {
            public DateOnly Day { get; set; }

            public int TotalBids { get; set; }

            public int VipBids { get; set; }

            public List<string> Rooms { get; set; }
        }

        public Bids_ByDay()
        {
            Map = bids => from bid in bids
                let days = bid.End.DayNumber - bid.Start.DayNumber + 1
                from day in Enumerable.Range(0, days)
                let d = bid.Start.AddDays(day)
                select new Entry
                {
                    Day = new DateOnly(d.Year, d.Month, d.Day),
                    TotalBids = 1,
                    VipBids = bid.Vip ? 1 : 0,
                    Rooms = new List<string> { bid.Room }
                };

            Reduce = results => from result in results
                group result by new
                {
                    result.Day
                }
                into g
                select new Entry
                {
                    Day = g.Key.Day,
                    TotalBids = g.Sum(x => x.TotalBids),
                    VipBids = g.Sum(x => x.VipBids),
                    Rooms = g.SelectMany(x => x.Rooms).Distinct().ToList()
                };

            StoreAllFields(FieldStorage.Yes);
        }
    }

The function StoreAllFields(FieldStorage.Yes) in RavenDB is used to define how certain fields are stored in the database. When using FieldStorage.Yes, you instruct the database to store all the fields defined in the result (in this case, Entry) so that they can be retrieved in queries without the need to recompute them.

Normally, in RavenDB, the result fields of an index are computed during indexing and are stored for faster query performance. However, only fields that have been marked to be stored will actually be stored and retrievable from the index. When you want to ensure that all fields are retrievable directly from the index at the push of a button, use the setting StoreAllFields(FieldStorage.Yes). This can be useful when you want to enable complete data retrieval from the index without further intervention or computations.

Bids_ByRoom Index

This Map-Reduce index aggregates the total number of bids and VIP bids per room across all days, listing the specific days on which bids were placed for that room.

It is used to determine the most popular rooms based on the frequency of bids and to analyze the bidding activity on specific days.

The index maps each bid to the corresponding room, assigning a total bid count of 1 for each bid. It also assigns a VIP bid count of 1 if a VIP customer places the bid, or 0 for regular bids. Additionally, the index lists the days the bid applies to, using a list of dates to represent the active days of the bid. 

The reduce function groups the data by room, summing the total bids and VIP bids for each room, and also aggregates the distinct days on which bids were placed.

This provides the capability to run queries that identify the most popular rooms based on total bidding activity, track which rooms are receiving the most interest from VIP customers, and optimize room pricing, availability, and marketing strategies based on bid frequency and customer demand.

internal class Bids_ByRoom : AbstractIndexCreationTask<Bid, Bids_ByRoom.Entry>
    {

internal class Entry
        {
            public string Room { get; set; }

            public int TotalBids { get; set; }

            public int VipBids { get; set; }

            public List<DateOnly> Days { get; set; }
        }

        public Bids_ByRoom()
        {
            Map = bids => from bid in bids
                let days = bid.End.DayNumber - bid.Start.DayNumber + 1
                select new Entry
                {
                    Room = bid.Room,
                    TotalBids = 1,
                    VipBids = bid.Vip ? 1 : 0,
                    Days = Enumerable.Range(0, days).Select(d => bid.Start.AddDays(d)).ToList()
                };

            Reduce = results => from result in results
                group result by new
                {
                    result.Room
                }
                into g
                select new Entry

                {
                    Room = g.Key.Room,
                    TotalBids = g.Sum(x => x.TotalBids),
                    VipBids = g.Sum(x => x.VipBids),
                    Days = g.SelectMany(x => x.Days).Distinct().OrderBy(x => x).ToList()
                };

            StoreAllFields(FieldStorage.Yes);
        }
}

Bids_ByDay_ByRoom Index

This “Fanout” index aggregates the total number of bids, the number of VIP bids, and whether a room received a VIP bid on each day, grouped by room and day.

In the Map function, each day a bid is active is calculated by iterating through the range from the start to the end day of the bid, ensuring each day is accounted for separately. For every day the bid is active, the function assigns a total bid count of 1. If a VIP customer places the bid, the function also assigns a VIP bid count of 1 and sets the VIP flag to true for that day. Additionally, the function associates it with the room that received it.

This multiplication of entries from a single source document (bid) to multiple output entries (one per covered day) is characteristic of the fanout principle.

The reduce function groups the data by room and day, summing the total bids and VIP bids for each room on each day. It also checks whether any VIP bids were made for a given room and day by using the Vip flag. This allows us to execute queries that identify the most popular rooms on particular days, track bidding activity for individual rooms, and analyze room-level bidding trends, including the impact of VIP customer activity.

This data can be useful for optimizing pricing and availability on a room-by-room basis in the hotel management system, allowing for more dynamic decision-making based on both general and VIP bidding trends.

internal class Bids_ByDay_ByRoom : AbstractIndexCreationTask<Bid, Bids_ByDay_ByRoom.Entry>
    {
        internal class Entry
        {
            public string Room { get; set; }

            public DateOnly Day { get; set; }

            public bool Vip { get; set; }

            public int TotalBids { get; set; }

            public int VipBids { get; set; }
        }

        public Bids_ByDay_ByRoom()
        {

            Map = bids => from bid in bids
                let days = bid.End.DayNumber - bid.Start.DayNumber + 1
                from day in Enumerable.Range(0, days)
                let d = bid.Start.AddDays(day)
                select new Entry

                {
                    Room = bid.Room,
                    Day = new DateOnly(d.Year, d.Month, d.Day),
                    Vip = bid.Vip,
                    TotalBids = 1,
                    VipBids = bid.Vip ? 1 : 0
                };

            Reduce = results => from result in results
                group result by new
                {
                    result.Room,
                    result.Day
                }
                into g
                select new Entry
                {
                    Room = g.Key.Room,
                    Day = g.Key.Day,
                    Vip = g.Any(x => x.Vip),
                    TotalBids = g.Sum(x => x.TotalBids),
                    VipBids = g.Sum(x => x.VipBids)
                };

            StoreAllFields(FieldStorage.Yes);
        }
    }

Reservations_ByDay Index

This index of the “Fanout” type splits reservations into individual days, allowing for efficient room availability queries. 

It is used to help customers check when a specific room is available.

The index maps each reservation to a list of days, based on the check-in (‘Start’) and check-out (‘End’) dates, creating an entry for each day that the room is reserved, for example, if a reservation is made for a room from January 1st to January 3rd, the index will create entries for January 1st, 2nd, and 3rd, indicating that the room is occupied on those days. 

This allows for efficient queries to check room availability on specific dates.

internal class Reservations_ByDay : AbstractIndexCreationTask<Reservation, Reservations_ByDay.Entry>
    {
        internal class Entry
        {
            public string Room { get; set; }

            public DateOnly Day { get; set; }
        }

        public Reservations_ByDay()
        {
            Map = reservations => from r in reservations
                let days = r.End.DayNumber - r.Start.DayNumber + 1
                from day in Enumerable.Range(0, days)
                let d = r.Start.AddDays(day)
                select new Entry
                {
                    Room = r.Room,
                    Day = new DateOnly(d.Year, d.Month, d.Day)
                };

            StoreAllFields(FieldStorage.Yes);
        }
    }
Key Takeaways:

Each index is carefully crafted to handle specific queries, reducing the time needed to retrieve data, for example, the Bids_ByDay index quickly aggregates bids per day, helping hotel managers spot demand trends, while the Reservations_ByDay index efficiently manages and checks room availability.

By using these targeted indexes, the system provides faster responses to queries, enabling real-time decision-making for hotel owners and enhancing the overall customer experience.

3. Optimizing Query Performance with Tailored Indexes

Efficient hotel reservation management requires rapid availability checks and insightful queries to track reservations, bidding activity, and room availability. 

To achieve this, RavenDB’s indexing capabilities are used to structure data efficiently and reduce the time needed to process complex queries.

Each index is carefully crafted to handle specific queries, reducing the time needed to retrieve data. For example, the Bids_ByDay index helps quickly aggregate bids per day, allowing hotel managers to spot demand trends, while the Reservations_ByDay index helps efficiently manage and check room availability. 

By using these targeted indexes, the system provides faster responses to queries, enabling real time decision-making for hotel owners and improving the overall customer experience.

Checking Room Availability

Customers often need to check if a specific room is available for a given date range. 

To handle this efficiently, we need to track all reservations and query their availability quickly.

Query:

void Show_Room101_January2022_Reservations()
{
    string room = "101";
    DateOnly jan1 = new DateOnly(2022, 1, 1);
    DateOnly jan31 = new DateOnly(2022, 1, 31);

    List<Reservations_ByDay.Entry> room101_jan2022_reservations = session
        .Query<Reservation, Reservations_ByDay>()
        .ProjectInto<Reservations_ByDay.Entry>()
        .Where(r => (r.Room == room) && (r.Day >= jan1 && r.Day <= jan31))
        .ToList();

    Console.WriteLine($"Reservations for Room {room} between {jan1} and {jan31}");
    foreach (var r in room101_jan2022_reservations)
    {
        Console.WriteLine($"Room {r.Room} - {r.Day}");
    }
}

The Reservations_ByDay index allows for efficient room availability queries by breaking down reservations into individual days. 

In this example, it helps quickly retrieve reservations for Room 101 in January 2022 by filtering on room and date. 

By using ProjectInto<Reservations_ByDay.Entry>, the query only fetches the necessary fields (room and day) from the index, rather than the entire reservation document, improving performance. 

The ProjectInto method helps by projecting the query results directly into a simplified structure defined by Reservations_ByDay.Entry, ensuring fast lookups and minimizing data retrieval, this approach is particularly useful in large datasets and real-time booking systems, as it avoids the overhead of loading unnecessary data.

Finding Available Time Slots for a Room

This query is similar to checking availability, but instead of looking for reservations, we look for open slots.

Query:

void Show_Free_Days_Room101_January2022()
{
    string room = "101";
    DateOnly jan1 = new DateOnly(2022, 1, 1);
    DateOnly jan31 = new DateOnly(2022, 1, 31);

    var room101_jan2022_reservations = session
        .Query<Reservation, Reservations_ByDay>()
        .ProjectInto<Reservations_ByDay.Entry>()
        .Where(r => (r.Room == room) && (r.Day >= jan1 && r.Day <= jan31))
        .ToList()
        .Select(r => r.Day);

    List<DateOnly> freeDaysJan2022 = Enumerable.Range(1, 31)
        .Select(d => new DateOnly(2022, 1, d))
        .Where(day => !room101_jan2022_reservations.Contains(day))
        .ToList();

    Console.WriteLine($"Free days for Room {room} between {jan1} and {jan31}");
    foreach (var day in freeDaysJan2022)
    {
        Console.WriteLine($"Date: {day}");
    }
}

The Reservations_ByDay index allows for efficient querying of room reservations, broken down by day. 

In this example, it helps quickly retrieve all the reservations for Room 101 in January 2022. 

By using ProjectInto<Reservations_ByDay.Entry>, the query only retrieves the necessary fields (room and day), avoiding the retrieval of full reservation documents, which improves performance.

The method subtracts the reserved days from the full set of days in January 2022 to determine the free days for Room 101, the result is a list of dates when the room is available. 

This approach ensures fast identification of open time slots, which is critical for real-time booking systems. 

The use of the index and ProjectInto helps reduce data load and increase query speed, especially in large datasets.

Identifying High-Demand Rooms (Bidding System)

Luxury hotels use a bidding system where the highest bidder secures a room. We need insights into which rooms receive the most bids.

Query:

void Show_Most_Bidded_Rooms()
{
    List<Bids_ByRoom.Entry> topBids = session
        .Query<Bid, Bids_ByRoom>()
        .ProjectInto<Bids_ByRoom.Entry>()
        .OrderByDescending(x => x.TotalBids)
        .ToList();

    Console.WriteLine("Rooms with most bids");
    foreach (var entry in topBids)
    {
        Console.WriteLine($"Room: {entry.Room} - Bids: {entry.TotalBids}");
    }
}

The Bids_ByRoom index aggregates the total number of bids per room, making it ideal for tracking which rooms are receiving the most interest. 

In this query, the use of ProjectInto<Bids_ByRoom.Entry> ensures that only the relevant fields (room and total bids) are retrieved, improving query performance by avoiding unnecessary data retrieval.

By ordering the results in descending order of total bids, the query efficiently identifies the most bidded rooms, this insight allows hotel owners to dynamically adjust room pricing based on demand, ensuring they maximize revenue for high-demand rooms. 

The Bids_ByRoom index and ProjectInto help optimize performance, particularly when dealing with large datasets, providing real-time insights into room popularity.

Identifying Peak Bidding Days for a Specific Room

For a specific room, hotel owners may want to know which days receive the most bids.

Query:

void Show_Room101_Most_Bidded_Days()
{
    string room = "101";

    List<Bids_ByDay_ByRoom.Entry> most_bidded_days = session
        .Query<Bid, Bids_ByDay_ByRoom>()
        .ProjectInto<Bids_ByDay_ByRoom.Entry>()
        .Where(r => r.Room == room)
        .OrderByDescending(x => x.TotalBids)
        .ToList();

    Console.WriteLine($"Most bidded days for Room {room}");
    foreach (var entry in most_bidded_days)
    {
        Console.WriteLine($"Date: {entry.Day} - Bids: {entry.TotalBids}");
    }
}

The Bids_ByDay_ByRoom index aggregates bids by day and room, allowing hotel owners to track which days receive the most bids for a specific room. 

In this query, the use of ProjectInto<Bids_ByDay_ByRoom.Entry> ensures that only the relevant data (room, day, and total bids) is retrieved, optimizing performance by reducing unnecessary document fetching.

By ordering the results in descending order of total bids, the query efficiently identifies the peak bidding days for Room 101, this helps hotel owners analyze trends and identify which days are the most popular for bidding, enabling dynamic pricing strategies and better resource allocation. The Bids_ByDay_ByRoom index and ProjectInto enhance performance, ensuring quick access to insights from large datasets.

To optimize operations, we need to track the busiest days across all rooms.

Query:

void Show_Most_Popular_Days()
{
    var most_bidded_days = session
        .Query<Bid, Bids_ByDay>()
        .ProjectInto<Bids_ByDay.Entry>()
        .OrderByDescending(x => x.TotalBids)
        .ToList();

    Console.WriteLine("Most bidded days");
    foreach (var entry in most_bidded_days)
    {
        Console.WriteLine($"Date: {entry.Day} - Bids: {entry.TotalBids} - Rooms: {String.Join(", ", entry.Rooms)}");
    }
}

The Bids_ByDay_ByRoom index aggregates bids by day and room, allowing hotel owners to track which days receive the most bids for a specific room. In this query, the use of ProjectInto<Bids_ByDay_ByRoom.Entry> ensures that only the relevant data (room, day, and total bids) is retrieved, optimizing performance by reducing unnecessary document fetching.

By ordering the results in descending order of total bids, the query efficiently identifies the peak bidding days for Room 101, this helps hotel owners analyze trends and identify which days are the most popular for bidding, enabling dynamic pricing strategies and better resource allocation. The Bids_ByDay_ByRoom index and ProjectInto enhance performance, ensuring quick access to insights from large datasets. 


Conclusion

By integrating RavenDB’s fanout indexes and map-reduce queries, we can create an efficient hotel reservation system that supports dynamic pricing, bidding analytics, and optimal resource planning.

This system ensures:

  • Fast queries for availability and reservations.
  • Optimized insights for hotel owners.
  • Efficient handling of high-demand periods.

Leveraging RavenDB’s powerful indexing capabilities allows for real-time availability checks, effective demand forecasting, and seamless customer experiences. 

Tracking bidding trends and identifying peak times empowers hotel owners to make data-driven decisions, maximizing revenue and optimizing room usage. 

This approach not only ensures performance at scale but also enables efficient management of large datasets, making the system a robust, scalable, and future-proof solution for modern hotel management.

Future Enhancements and Expansion

Looking ahead, the hotel reservation system can be further expanded to include additional features that enhance both operational efficiency and customer experience. 

One potential improvement is the integration of price recommendation algorithms based on bidding trends, allowing the system to automatically adjust room prices based on demand, competition, and historical trends. 

Another area of ​​expansion could involve implementing dynamic pricing models that take into account factors such as seasonality, local events, and real-time booking data, optimizing revenue management.

Additionally, the system can be integrated with external tools and services, such as third-party analytics platforms, customer relationship management (CRM) systems, or even weather data services, to provide even more tailored pricing strategies and marketing campaigns. 

By leveraging external data sources, hotel owners can gain deeper insights into customer preferences and booking patterns, helping them make more informed decisions.

As technology evolves, integrating machine learning and artificial intelligence into the system could further improve predictive capabilities, offering even more accurate forecasting for room demand and bidding behavior. 

These advancements would ensure that the system remains scalable, adaptive, and aligned with the latest industry trends, ultimately offering enhanced value to both hotel owners and guests.

Woah, already finished? 🤯

If you found the article interesting, don’t miss a chance to try our database solution – totally for free!

Try now try now arrow icon