8000 GitHub - MstfTurgut/hotel-reservation-system: a modular monolith hotel software with DDD, CQRS, Ports & Adapters and Event-Driven support
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

MstfTurgut/hotel-reservation-system

Repository files navigation

Hotel Reservation System

A modular monolith hotel software with DDD, CQRS, Ports & Adapters and Event-Driven support


This project took a lot of time and effort. If you like it, please consider giving it a ⭐ star!


Table of Contents

1. Event Storming

2. UML Modeling

3. Scenarios

4. Key Assumptions

5. Modules & Integration

6. Pattern Usage

  6.1 Ports & Adapters

  6.2 CQRS

  6.3 SAGA

  6.4 Design Patterns

  6.5 Aggregate Tactical Pattern

    6.5.1 Raising Domain Events Inside The Aggregate

7. Testing


1. Event Storming

EventStorming is a flexible workshop format for collaborative exploration of complex business domains.

Although event storming is typically a collaborative process involving the entire team, in this project I applied it individually. Although not collaborative in this case, the process still provided clarity and structure to the design.


event-storming

You can explore the full event storming board in detail here.


2. UML Modeling


“A model is a selectively simplified and consciously structured form of knowledge.”
- Eric Evans, Domain Driven Design, Page 12


The system has undergone significant evolution since my initial modeling attempts, which began with simple paper sketches.

Although the domain itself is relatively straightforward, the design process presented several complex decisions.

One notable example was the choice to define RoomType as a separate aggregate—an approach that required considerable deliberation.

Another challenging decision involved removing the Availability class in favor of dynamically checking existing reservations when evaluating the availability of a reservation request. These are just a few examples of the many thoughtful trade-offs made throughout the design process.

The image below displays some of the early conceptual sketches created at the outset of the project. At that time, RoomType was treated merely as an enumeration, and the Availability class was directly associated with the Room.


papers



The current version of the UML model is shown below:


uml-model1



uml-model2



saga1



The complete UML model is available here.


3. Scenarios

Online Reservation

Reservation made directly by the customer online.

Flow:

  1. Customer visits the booking site and selects dates and number of guests.

  2. Site checks available rooms and returns suitable room types via:

    GET /api/reservations/availability
    
  3. Customer selects room type, enters personal and card details.

  4. Site sends request to:

    POST /api/reservations/create-online
    
  5. The system sends the reservation code, confirmation code and other details to the customer via SMS and email.


Reservation with Call

This refers to a reservation made over the phone by a customer, where the receptionist inputs the details manually.

Flow:

  1. Customer calls and provides necessary reservation details (dates, name, contact info, number of guests, etc.).

  2. Receptionist checks room availability via:

    GET /api/reservations/availability
    
  3. Receptionist confirms availability and enters reservation details using:

    POST /api/reservations/create-online
    
  4. The system sends the reservation code, confirmation code and other details to the customer via SMS and email.


Face-to-Face Reservation (Typically Same-Day Check-In)

Customer walks in and wants to book and check in immediately.

Flow:

  1. Customer provides check-in/check-out dates, guest count.

  2. Receptionist checks availability:

    GET /api/reservations/availability
    
  3. Receptionist creates the reservation:

    POST /api/reservations/create-in-hotel
    
  4. Receptionist receives payment through cash or card

  5. The system sends the reservation code, confirmation code and other details to the customer via SMS and email.


Online Cancellation

Customer logs in and cancels their reservation online.

Flow:

  1. Customer logs into their portal.

  2. System fetches user reservations:

    GET /api/reservations/user
    
  3. User selects reservation to cancel and enters confirmation code.

  4. System sends cancellation request:

    PUT /api/reservations/{reservationId}/cancel
    

Cancellation by Call

Customer calls to cancel their reservation.

Flow:

  1. Customer gives reservation info (code or name + phone).

  2. Receptionist fetches reservation:

    GET /api/reservations/{reservationCode}
    OR
    POST /api/reservations/customer
    
  3. Receptionist asks for confirmation code.

  4. Receptionist cancels the reservation:

    PUT /api/reservations/{reservationId}/cancel
    

Customer Checking In

Customer arrives and wants to check in.

Flow:

  1. Customer provides reservation code or name and phone number.

  2. Receptionist fetches the reservation:

    GET /api/reservations/{reservationCode}
    OR
    POST /api/reservations/customer
    
  3. Receptionist asks for confirmation code.

  4. Receptionist checks in the customer:

    PUT /api/reservations/{reservationId}/check-in
    
  5. Receptionist fetches room details:

    GET /api/rooms/{roomId}
    
  6. Receptionist gives keys to the guest.


Customer Checking Out

Customer wants to check out.

Flow:

  1. Customer provides reservation code or name and phone.

  2. Receptionist fetches reservation:

    GET /api/reservations/{reservationCode}
    OR
    POST /api/reservations/customer
    
  3. Receptionist checks out the reservation:

    PUT /api/reservations/{reservationId}/check-out
    
  4. Reservation is marked as checked-out, making the room available for future reservations.

  5. Receptionist collects keys.


4. Key Assumptions

  • A module may only depend on the integration submodule (e.g., facades, integration events) of another module. (This is explained in detail in the Modules & Integration section.)

  • The domain model or read model of a module must not be accessed or used by other modules. Instead, communication should occur through well-defined contracts.

  • Each module maintains its own data in a separate schema. Shared data between modules is not allowed.

  • All assumptions and guidelines regarding aggregates from the Domain-Driven Design (DDD) book apply.

  • Business logic must not leak into the application layer. (Follow the principle of "Tell, Don’t Ask.")

  • Dependencies of both the domain submodule and the integration submodule must be inverted, adhering to the Dependency Inversion Principle.

  • Each module must modify only one aggregate instance per transaction.


"And a properly designed bounded context modifies only one aggregate instance per transaction in all cases."
- Vaughn Vernon, Effective Aggregate Design Part 1


When a transaction involves multiple aggregates or modules, we rely on eventual consistency.

However, in this project, we aren't even encountering typical issues related to eventual consistency, since it's currently implemented as a modular monolith.


5. Modules & Integration

Module descriptions

  • Identity Access: Responsible for user authentication and provides access to current user information for other modules.

  • Reservation (core) : Provides the main features of the application (e.g., create, cancel, check-in, etc.).

  • Room Management: Enables managers to manage rooms and room types (e.g., add, remove, etc.).

  • Payment: Handles payment processing and storage.

  • Notification: Sends notifications to customers via SMS and email for various events.

  • Shared: Contains common interfaces and shared domain (kernel) objects. Major modules depend on this module to reuse boilerplate code.

  • Security: Handles endpoint filtering and authorization.

  • Applicationmain: Responsible for starting the entire application.


Integration

I paid close attention to managing the dependencies between modules and made a strong effort to keep them loosely coupled.

Each module that is expected to be used by others contains an integration submodule. This submodule includes elements such as facades, integration events, and contracts, which serve as translators between dependent modules. The dependencies of the integration submodule are inverted; in other words, it has no dependencies on any other submodules within its own module, nor on any external modules. Service-like classes such as facades are defined as abstract within the integration submodule and are implemented by other submodules as needed.

To demonstrate how modules communicate, here's an example from this project which involves reservation cancellation and how it's handled by the payment module.

Below is the integration submodule of the reservation module:;


integration-submodule


One of the integration events in the integration-reservation submodule is ReservationCancelledIntegrationEvent. This event is published by a domain event handler in the application-reservation submodule.


it-event


This event is then handled within the payment module, as shown below:


it-event-handler


The payment module processes this event by depending solely on the integration-reservation submodule of the reservation module. Because the dependencies of the integration submodule are inverted, this approach minimizes coupling between the modules.


6. Pattern Usage

This part contains which patterns (high level and low level) are used in this project.


6.1 Ports & Adapters

Most of the modules in this project use ports and adapters pattern.

Ports and Adapters is a software architecture pattern that aims to isolate the core logic of an application from external systems such as databases, user interfaces, messaging systems, and other services. It achieves this separation by introducing "ports" as interfaces through which the core application communicates, and "adapters" as implementations of those interfaces for specific technologies or infrastructures.


"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases."
- Alistair Cockburn, Hexagonal architecture the original 2005 article


hexagonal



6.2 CQRS

Command Query Responsibility Segregation (CQRS) is a design pattern that segregates read and write operations for a data store into separate data models. This approach allows each model to be optimized independently and can improve the performance, scalability, and security of an application.

There are various ways to implement the CQRS pattern. Below is the approach used in this project:

cqrs


As shown below, each controller uses command/query buses to dispatch incoming requests to the corresponding command/query handler.


controller



6.3 SAGA

The two typical saga implementation approaches are choreography and orchestration. Each approach has its own set of challenges and technologies to coordinate the workflow.

In the choreography approach, services exchange events without a centralized controller. With choreography, each local transaction publishes domain events that trigger local transactions in other services.

I used the choreography approach for this project. Below is the model illustrating the choreography-based saga:


choreography-pattern



Here is the event flow model of this project:

saga1



saga2



saga3

8000

saga4



If you want to have a closer look, you can access to the event flow from here.


The screenshot below shows the console output log when a 'Create Online Reservation' request is made to the API. The event flow can also be observed in the logs.


log-console



6.4 Design Patterns

While designing this project, certain parts seemed well-suited for the use of specific design patterns. For those parts, I applied the appropriate pattern. Here's an example from the notification module of the project :


strategy



strategy-factory




6.5 Aggregate Tactical Pattern

The Aggregate is one of the key tactical patterns introduced in the book Domain-Driven Design by Eric Evans. It helps manage complexity and maintain consistency within the domain model.


Book definition:

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE. The root is a single specific ENTITY contained in the AGGREGATE. The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, although objects within the boundary may hold references to each other


6.5.1 Raising Domain Events Inside The Aggregate


"Instead of dispatching to a domain event handler immediately, what if instead we recorded our domain events, and before committing our transaction, dispatch those domain events at that point? This will have a number of benefits, besides us not tearing our hair out."
- Jimmy Boggard, A better domain events pattern, 2014


In this approach, domain events are not published immediately when they occur. Instead, they are recorded within the aggregate root during the execution of business logic. These recorded events are then dispatched only after the aggregate has been persisted—ensuring consistency between the state changes and the events they produce.

To support this, all aggregate roots inherit from a shared abstract base class that provides the functionality to register domain events:


aggregate-root


Below is an example from the Reservation aggregate root. This method demonstrates how a domain event is raised internally as part of the aggregate's behavior:


reservation-cancel


This pattern ensures that domain events remain tightly coupled with the aggregate's lifecycle, promoting consistency, maintaining encapsulation, and reducing the risk of side effects during business operations.


7. Testing

Unit Testing

Unit tests focus on verifying business behaviors, not just individual methods or classes. Each test should reflect a meaningful scenario within the domain, ideally something that resonates with a business user. These tests are fast, isolated, and help validate the rules and logic embedded in aggregates and domain services.


Samples :


cancel-unit



check-in-unit


Integration Testing

Integration tests validate how multiple components of the system work together — such as repositories, external services, message handlers, and infrastructure code. These tests ensure that the system behaves correctly when all the pieces are wired together, often involving databases, HTTP endpoints, or messaging systems.


Samples :


auth-integration



delete-reservation-it



create-reservation-fake-handler



create-online-it




Mustafa Turgut
mstftrgt00@gmail.com

About

a modular monolith hotel software with DDD, CQRS, Ports & Adapters and Event-Driven support

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

0