Loading content...
We've designed individual components—entities, pricing engine, availability management, patterns. Now it's time to see how they work together in real scenarios. This walkthrough traces complete booking workflows from start to finish, showing component interactions, data flow, and error handling.
This is the synthesis that transforms isolated pieces into a cohesive system.
By the end of this page, you will see complete end-to-end flows for booking creation, modification, cancellation, and check-in/check-out. You'll understand how components communicate, how errors propagate, and how the system maintains consistency throughout complex operations.
Before diving into scenarios, let's visualize the overall system structure.
| Component | Responsibility | Key Dependencies |
|---|---|---|
| BookingAPI | HTTP endpoints for booking operations | BookingService, Auth |
| BookingService | Orchestrates booking workflows | Factory, Pricing, Availability |
| ReservationFactory | Creates properly initialized reservations | PricingEngine, AvailabilityService |
| PricingEngine | Calculates rates, taxes, promotions | RateRepository, TaxCalculator |
| AvailabilityService | Manages temporal room inventory | InventoryRepository, LockManager |
| Reservation (State) | Enforces lifecycle transitions | Domain events, validation |
| EventBus | Publishes domain events | ChannelManager, NotificationService |
| ChannelManager | Syncs availability to OTAs | OTA connectors, InventoryService |
Scenario: A guest searches for available rooms, selects one, and completes a booking with payment.
Setup:
Guest enters search criteria on the website.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
// 1. API receives search request@Get('/hotels/:hotelId/availability')async searchAvailability( @Param('hotelId') hotelId: string, @Query() query: SearchQueryDto): Promise<AvailabilityResponse> { // Validate input const validatedQuery = this.validateSearchQuery(query); // 2. SearchService processes request const results = await this.searchService.search({ hotelId: HotelId.from(hotelId), checkIn: LocalDate.parse(query.checkIn), checkOut: LocalDate.parse(query.checkOut), adults: query.adults, children: query.children, ratePlanCode: query.ratePlanCode, }); return this.mapToResponse(results);} // 3. SearchService implementationclass SearchService { async search(query: SearchQuery): Promise<SearchResult[]> { const hotel = await this.hotelRepository.findById(query.hotelId); // Get all room types for this hotel const roomTypes = hotel.getRoomTypes() .filter(rt => rt.canAccommodate(query.adults, query.children)); const results: SearchResult[] = []; for (const roomType of roomTypes) { // 4. Check availability across all nights const availability = await this.availabilityService.checkAvailability( hotel.id, roomType.id, query.checkIn, query.checkOut ); if (availability.availableRooms > 0) { // 5. Calculate pricing const pricing = await this.pricingEngine.calculatePricing({ hotel, roomType, ratePlan: await this.getRatePlan(query.ratePlanCode), stayDates: new DateRange(query.checkIn, query.checkOut), nights: query.nights, adults: query.adults, children: query.children, promoCode: null, currency: hotel.defaultCurrency, context: this.buildContext(query), }); results.push({ roomType: roomType.toSummary(), availableRooms: availability.availableRooms, pricing: pricing.toSummary(), cancellationPolicy: pricing.cancellationPolicy.toSummary(), }); } } // Sort by price return results.sort((a, b) => a.pricing.total.compareTo(b.pricing.total) ); }} // Response for our scenario:{ "roomTypes": [ { "id": "DELUXE-KING", "name": "Deluxe King Room", "description": "450 sq ft with city view", "maxOccupancy": 3, "availableRooms": 5, "pricing": { "nightlyRates": [ { "date": "2024-03-15", "rate": 189.00 }, { "date": "2024-03-16", "rate": 219.00 }, // Weekend { "date": "2024-03-17", "rate": 199.00 } ], "subtotal": 607.00, "ratePlanDiscount": -60.70, // 10% AAA "taxes": 72.84, "fees": 45.00, // Resort fee "total": 664.14 }, "cancellationPolicy": { "name": "Flexible 24-Hour", "description": "Free cancellation until 24 hours before check-in" } }, // ... more room types ]}Guest selects Deluxe King and proceeds to payment.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
// 1. API receives booking request@Post('/bookings')async createBooking( @Body() dto: CreateBookingDto, @User() user: AuthenticatedUser): Promise<BookingResponse> { // Validate input const validatedInput = this.validateBookingInput(dto); // 2. BookingService orchestrates the flow const result = await this.bookingService.createBooking({ ...validatedInput, userId: user?.id, }); return this.mapToResponse(result);} // 3. BookingService implementationclass BookingService { async createBooking(input: CreateBookingInput): Promise<BookingResult> { // Start transaction for consistency return this.transactionManager.run(async (tx) => { // 4. Factory creates reservation with all validations const reservation = await this.reservationFactory.create({ hotelId: input.hotelId, roomTypeId: input.roomTypeId, checkInDate: input.checkInDate, checkOutDate: input.checkOutDate, adults: input.adults, children: input.children, childAges: input.childAges, roomCount: 1, guest: input.guestDetails, ratePlanCode: input.ratePlanCode, promotionCode: input.promoCode, channel: BookingChannel.DIRECT, source: 'Website', specialRequests: input.specialRequests, }); // 5. Process payment const paymentResult = await this.paymentService.processPayment({ reservationId: reservation.id, amount: reservation.totalAmount, paymentMethod: input.paymentMethod, billingDetails: input.billingDetails, }); if (!paymentResult.success) { // Rollback inventory reservation await this.availabilityService.releaseInventory( reservation.hotelId, reservation.roomTypeId, reservation.checkInDate, reservation.checkOutDate, reservation.roomCount, reservation.id ); throw new PaymentFailedError(paymentResult.errorMessage); } // 6. Confirm reservation (State transition) reservation.recordPayment(paymentResult.paymentId, paymentResult.amount); reservation.confirm(); // PENDING → CONFIRMED // 7. Save reservation await this.reservationRepository.save(reservation, tx); // 8. Publish domain events (outside transaction) this.eventBus.publishAll(reservation.domainEvents); return new BookingResult({ reservation, confirmationNumber: reservation.confirmationNumber, paymentConfirmation: paymentResult.confirmationCode, }); }); }} // 9. Event handlers react to ReservationConfirmedEventclass ReservationConfirmedHandler implements EventHandler<ReservationConfirmedEvent> { async handle(event: ReservationConfirmedEvent): Promise<void> { // Send confirmation email await this.emailService.sendConfirmation( event.reservationId, event.guestEmail ); // Update availability on OTAs await this.channelManager.syncAvailability( event.hotelId, event.roomTypeId, event.checkInDate, event.checkOutDate ); // Award loyalty points (if applicable) if (event.loyaltyMemberId) { await this.loyaltyService.awardBookingPoints( event.loyaltyMemberId, event.totalAmount ); } }} // Final response:{ "confirmationNumber": "GH-NYC-2024031501234", "status": "CONFIRMED", "guestName": "John Smith", "hotel": { "name": "Grand Hotel NYC", "address": "123 Main St, New York, NY 10001" }, "checkIn": "2024-03-15", "checkOut": "2024-03-18", "roomType": "Deluxe King Room", "guests": { "adults": 2, "children": 0 }, "pricing": { "roomTotal": 546.30, "taxes": 72.84, "fees": 45.00, "grandTotal": 664.14 }, "cancellationPolicy": "Free cancellation until March 14, 2024 3:00 PM", "payment": { "status": "COMPLETED", "lastFour": "4242" }}Scenario: John Smith extends his stay by one night (now March 15-19).
Challenges:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
// 1. API receives modification request@Patch('/bookings/:confirmationNumber')async modifyBooking( @Param('confirmationNumber') confirmationNumber: string, @Body() dto: ModifyBookingDto): Promise<ModificationResponse> { const result = await this.bookingService.modifyBooking( ConfirmationNumber.from(confirmationNumber), dto ); return this.mapToResponse(result);} // 2. BookingService handles modificationclass BookingService { async modifyBooking( confirmationNumber: ConfirmationNumber, changes: ModificationInput ): Promise<ModificationResult> { // Find existing reservation const reservation = await this.reservationRepository .findByConfirmationNumber(confirmationNumber); if (!reservation) { throw new ReservationNotFoundError(confirmationNumber); } // 3. State pattern validates modification is allowed if (!reservation.isModifiable()) { throw new ModificationNotAllowedError( `Cannot modify reservation in ${reservation.statusName} state` ); } return this.transactionManager.run(async (tx) => { // 4. Analyze date changes const dateAnalysis = this.dateAnalyzer.analyze( reservation.originalDates, new DateRange( changes.newCheckIn ?? reservation.checkInDate, changes.newCheckOut ?? reservation.checkOutDate ) ); // 5. Check availability for new nights if (dateAnalysis.addedNights.length > 0) { const availability = await this.availabilityService .checkAvailabilityForNights( reservation.hotelId, reservation.roomTypeId, dateAnalysis.addedNights ); if (availability.minAvailable < reservation.roomCount) { throw new InsufficientInventoryError( `Cannot extend - only ${availability.minAvailable} rooms available on ${availability.constrainingDate}` ); } } // 6. Calculate new pricing const repricingResult = await this.repricingService .calculateDateModificationPricing( reservation, changes.newCheckIn ?? reservation.checkInDate, changes.newCheckOut ?? reservation.checkOutDate ); // 7. Adjust inventory await this.availabilityService.adjustInventoryForModification( reservation.id, reservation.currentDates, new DateRange( changes.newCheckIn ?? reservation.checkInDate, changes.newCheckOut ?? reservation.checkOutDate ), reservation.hotelId, reservation.roomTypeId, reservation.roomCount ); // 8. Apply changes to reservation reservation.modifyDates( changes.newCheckIn ?? reservation.checkInDate, changes.newCheckOut ?? reservation.checkOutDate ); reservation.updatePricing(repricingResult); // 9. Handle payment difference if (repricingResult.isAdditionalPaymentRequired) { await this.processAdditionalPayment( reservation, repricingResult.difference ); } else if (repricingResult.isRefundDue) { await this.processRefund( reservation, repricingResult.difference.abs() ); } // 10. Save and publish events await this.reservationRepository.save(reservation, tx); this.eventBus.publishAll(reservation.domainEvents); return new ModificationResult({ reservation, originalDates: dateAnalysis.originalDates, newDates: dateAnalysis.newDates, priceDifference: repricingResult.difference, newTotal: repricingResult.newTotal, }); }); }} // Repricing details for our scenario:{ "modification": "DATE_EXTENSION", "originalDates": { "checkIn": "2024-03-15", "checkOut": "2024-03-18" }, "newDates": { "checkIn": "2024-03-15", "checkOut": "2024-03-19" }, "pricingBreakdown": { "unchangedNights": { "nights": ["2024-03-15", "2024-03-16", "2024-03-17"], "total": 546.30, "note": "Original rates preserved" }, "addedNights": { "nights": ["2024-03-18"], "total": 189.00, "note": "Current market rate (Monday)" }, "previousTotal": 664.14, "newTotal": 876.89, "difference": 212.75, "additionalPaymentRequired": true }}The entire modification—availability adjustment, repricing, payment processing—happens in a single transaction. If payment fails, we roll back inventory and leave the reservation unchanged. This all-or-nothing approach prevents inconsistent states.
Scenario: Guest cancels 12 hours before check-in. The cancellation policy says: free until 24 hours, 100% charge within 24 hours.
Key Behavior:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
// 1. API receives cancellation request@Delete('/bookings/:confirmationNumber')async cancelBooking( @Param('confirmationNumber') confirmationNumber: string, @Body() dto: CancelBookingDto): Promise<CancellationResponse> { const result = await this.bookingService.cancelBooking( ConfirmationNumber.from(confirmationNumber), dto.reason ); return this.mapToResponse(result);} // 2. BookingService handles cancellationclass BookingService { async cancelBooking( confirmationNumber: ConfirmationNumber, reason: string ): Promise<CancellationResult> { const reservation = await this.reservationRepository .findByConfirmationNumber(confirmationNumber); if (!reservation) { throw new ReservationNotFoundError(confirmationNumber); } // 3. State pattern checks if cancellation is allowed if (!reservation.isCancellable()) { throw new CancellationNotAllowedError( `Cannot cancel reservation in ${reservation.statusName} state` ); } return this.transactionManager.run(async (tx) => { // 4. State.cancel() handles the transition and penalty calculation // This delegates to the current state object (ConfirmedState) const cancellationResult = reservation.cancel(reason); // Inside ConfirmedState.cancel(): // - Calculates hours until check-in: 12 hours // - Finds policy tier: 24-hour threshold → 100% penalty // - Calculates penalty: $876.89 (full charge) // - Processes refund: $0 (paid $876.89, penalty $876.89) // - Releases inventory // - Transitions to CancelledState // - Emits ReservationCancelledEvent // 5. Save reservation await this.reservationRepository.save(reservation, tx); // 6. Publish events this.eventBus.publishAll(reservation.domainEvents); return cancellationResult; }); }} // 7. Inside ConfirmedState.cancel() - detailed viewclass ConfirmedState extends ReservationState { cancel(reason: string): CancellationResult { // Calculate penalty using CancellationCalculator (Template Method) const penalty = this.cancellationCalculator.calculatePenalty( this.context, // reservation Instant.now() // cancellation time ); // Calculate refund const amountPaid = this.context.totalPaid; const refundAmount = amountPaid.subtract(penalty.adjustedPenalty); const result = new CancellationResult({ reservationId: this.context.id, cancelledAt: Instant.now(), penaltyAmount: penalty.adjustedPenalty, refundAmount: refundAmount.max(Money.zero()), policyApplied: this.context.cancellationPolicy.name, reason, }); // Release inventory back to available pool this.context.releaseInventory(); // Process refund (if any) if (refundAmount.isPositive()) { this.context.initiateRefund(refundAmount); } // Record status change with history this.context.recordStatusChange( 'CONFIRMED', 'CANCELLED', `Cancelled: ${reason}. Penalty: ${penalty.adjustedPenalty}` ); // Transition to terminal state this.context.transitionTo(new CancelledState()); // Emit domain event this.context.addDomainEvent(new ReservationCancelledEvent( this.context.id, this.context.confirmationNumber, result )); return result; }} // 8. CancellationCalculator (Template Method) - penalty calculationclass StandardCancellationCalculator extends CancellationCalculator { protected calculateHoursUntilCheckin( reservation: Reservation, cancellationTime: Instant ): number { const checkinInstant = reservation.checkInDate .atTime(LocalTime.of(15, 0)) // 3 PM check-in .atZone(reservation.hotel.timezone) .toInstant(); return cancellationTime.until(checkinInstant, ChronoUnit.HOURS); // Returns: 12 hours } protected calculateBasePenalty( reservation: Reservation, tier: CancellationTier ): Money { // Tier: { hoursBeforeCheckin: 24, penaltyPercentage: 100 } // Since we're at 12 hours (< 24), this tier applies return reservation.totalAmount.multiply(tier.penaltyPercentage / 100); // Returns: $876.89 * 100% = $876.89 }} // Response for our scenario:{ "confirmationNumber": "GH-NYC-2024031501234", "status": "CANCELLED", "cancelledAt": "2024-03-15T03:00:00Z", "reason": "Change of travel plans", "cancellationPolicy": { "name": "24-Hour Flexible", "tierApplied": "Within 24 hours", "penalty": "100% of booking" }, "financial": { "originalTotal": 876.89, "penaltyAmount": 876.89, "refundAmount": 0.00, "refundStatus": "NOT_APPLICABLE" }, "inventoryReleased": true}The cancellation penalty is calculated at cancellation time, not booking time. If the guest had cancelled 36 hours before check-in (before the 24-hour threshold), they would have received a full refund. The time-based tier system is critical for correct policy application.
Scenario: Different guest (not John, who cancelled) arrives for their reservation and goes through check-in, stay, and check-out.
Key Operations:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
// ==================== CHECK-IN ==================== // 1. Front desk initiates check-in@Post('/reservations/:confirmationNumber/check-in')async checkIn( @Param('confirmationNumber') confirmationNumber: string, @Body() dto: CheckInDto): Promise<CheckInResponse> { return this.frontDeskService.checkIn( ConfirmationNumber.from(confirmationNumber), dto.assignedRoomNumbers );} // 2. FrontDeskService handles check-inclass FrontDeskService { async checkIn( confirmationNumber: ConfirmationNumber, roomNumbers: RoomNumber[] ): Promise<CheckInResult> { const reservation = await this.reservationRepository .findByConfirmationNumber(confirmationNumber); if (!reservation) { throw new ReservationNotFoundError(confirmationNumber); } return this.transactionManager.run(async (tx) => { // 3. Find and validate rooms const rooms: Room[] = []; for (const roomNumber of roomNumbers) { const room = await this.roomRepository.findByNumber( reservation.hotelId, roomNumber ); if (!room) { throw new RoomNotFoundError(roomNumber); } // Validate room type matches if (!room.roomTypeId.equals(reservation.roomTypeId)) { throw new RoomTypeMismatchError( `Room ${roomNumber} is not a ${reservation.roomTypeName}` ); } // Validate room is available if (!room.canBeAssigned()) { throw new RoomNotAvailableError( `Room ${roomNumber} is ${room.status}` ); } rooms.push(room); } // 4. Assign rooms (Room entity updates) for (const room of rooms) { room.assignToReservation( reservation.id, reservation.primaryGuestId ); await this.roomRepository.save(room, tx); } // 5. Check in reservation (State transition) reservation.checkIn(rooms.map(r => r.id)); // ConfirmedState.checkIn() does: // - Validates room count matches reservation // - Sets assignedRoomIds // - Sets checkedInAt timestamp // - Transitions to CheckedInState // - Records status change in history // - Emits GuestCheckedInEvent // 6. Save reservation await this.reservationRepository.save(reservation, tx); // 7. Publish events this.eventBus.publishAll(reservation.domainEvents); return new CheckInResult({ reservation, assignedRooms: rooms.map(r => r.toSummary()), keyCardsIssued: rooms.length, }); }); }} // ==================== DURING STAY ==================== // 8. Post incidental charges@Post('/reservations/:confirmationNumber/charges')async postCharge( @Param('confirmationNumber') confirmationNumber: string, @Body() dto: PostChargeDto): Promise<ChargeResponse> { const reservation = await this.reservationRepository .findByConfirmationNumber(confirmationNumber); // Only CHECKED_IN reservations can have charges posted if (reservation.statusName !== 'CHECKED_IN') { throw new InvalidStateError( 'Can only post charges for checked-in guests' ); } const charge = new IncidentalCharge({ type: dto.chargeType, // MINIBAR, ROOM_SERVICE, SPA, etc. description: dto.description, amount: Money.of(dto.amount, dto.currency), postedAt: Instant.now(), postedBy: dto.staffId, }); reservation.addCharge(charge); await this.reservationRepository.save(reservation); return this.mapToResponse(charge);} // ==================== CHECK-OUT ==================== // 9. Front desk initiates check-out@Post('/reservations/:confirmationNumber/check-out')async checkOut( @Param('confirmationNumber') confirmationNumber: string): Promise<CheckOutResponse> { return this.frontDeskService.checkOut( ConfirmationNumber.from(confirmationNumber) );} // 10. FrontDeskService handles check-outclass FrontDeskService { async checkOut( confirmationNumber: ConfirmationNumber ): Promise<CheckOutResult> { const reservation = await this.reservationRepository .findByConfirmationNumber(confirmationNumber); return this.transactionManager.run(async (tx) => { // 11. Check for outstanding balance const balance = reservation.outstandingBalance; if (balance.isPositive()) { // Attempt to charge card on file const paymentResult = await this.paymentService.chargeBalance( reservation.id, balance ); if (!paymentResult.success) { throw new OutstandingBalanceError( `Cannot check out. Balance: ${balance}. ` + `Payment failed: ${paymentResult.errorMessage}` ); } reservation.recordPayment( paymentResult.paymentId, paymentResult.amount ); } // 12. Check out reservation (State transition) reservation.checkOut(); // CheckedInState.checkOut() does: // - Validates no outstanding balance // - Sets checkedOutAt timestamp // - Transitions to CheckedOutState // - Emits GuestCheckedOutEvent // 13. Release rooms (Room entity updates) for (const roomId of reservation.assignedRoomIds) { const room = await this.roomRepository.findById(roomId); room.release(); // Status → NEEDS_CLEANING await this.roomRepository.save(room, tx); } // 14. Record stay for guest history and loyalty const guest = await this.guestRepository.findById( reservation.primaryGuestId ); guest.recordStay( LocalDate.now(), reservation.totalCharged ); guest.awardPoints( this.loyaltyCalculator.calculatePoints(reservation), 'Completed stay' ); await this.guestRepository.save(guest, tx); // 15. Save reservation await this.reservationRepository.save(reservation, tx); // 16. Publish events this.eventBus.publishAll(reservation.domainEvents); return new CheckOutResult({ reservation, finalBill: reservation.getFinalBill(), loyaltyPointsEarned: guest.lastPointsAwarded, }); }); }} // 17. Housekeeping service reacts to room releaseclass RoomReleasedHandler implements EventHandler<GuestCheckedOutEvent> { async handle(event: GuestCheckedOutEvent): Promise<void> { for (const roomId of event.roomIds) { // Create housekeeping task await this.housekeepingService.createCleaningTask(roomId); } }}Production systems must handle failures gracefully. Here's how the Hotel Booking System handles common error scenarios:
| Error Scenario | Detection Point | Handling Strategy | User Experience |
|---|---|---|---|
| Payment fails after inventory reserved | PaymentService | Rollback inventory reservation in catch block | Clear error message, guest can retry with different card |
| Inventory sold out between search and book | AvailabilityService.reserve() | InsufficientInventoryError thrown | Prompt to choose alternative dates/room types |
| OTA booking creates duplicate | Channel Manager | Unique constraint on external ref, detect and reject | OTA handles retry; our system protected |
| Database timeout during booking | Transaction manager | Automatic rollback, retry with exponential backoff | Loading indicator, eventual success or clear failure |
| Rate changed between display and booking | Booking validation | Compare displayed vs. calculated, warn if > threshold | Price change notification, confirm to proceed |
| Guest tries to cancel CHECKED_IN reservation | State pattern | InvalidStateTransitionError from CheckedInState | Message: 'Please contact front desk for early departure' |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
// Comprehensive error handling in BookingServiceclass BookingService { async createBooking(input: CreateBookingInput): Promise<BookingResult> { // Wrap in transaction with error handling try { return await this.transactionManager.run(async (tx) => { // ... booking logic }); } catch (error) { // Log full error for debugging this.logger.error('Booking failed', { input, error: error.message, stack: error.stack, }); // Map to user-friendly errors if (error instanceof InsufficientInventoryError) { throw new BookingError( 'SOLD_OUT', 'Sorry, this room type is no longer available. Please choose different dates or room type.', { retryable: false } ); } if (error instanceof PaymentFailedError) { throw new BookingError( 'PAYMENT_FAILED', error.message, { retryable: true, retryAction: 'UPDATE_PAYMENT' } ); } if (error instanceof InvalidDateRangeError) { throw new BookingError( 'INVALID_DATES', error.message, { retryable: false } ); } // Unexpected errors - don't expose internal details throw new BookingError( 'SYSTEM_ERROR', 'An unexpected error occurred. Please try again.', { retryable: true } ); } }} // Race condition protection - optimistic lockingclass AvailabilityService { async reserveInventory(/* params */): Promise<void> { const MAX_RETRIES = 3; for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { await this.lockManager.withLock(lockKey, async () => { // Re-check availability with lock held const current = await this.checkAvailability(/* params */); if (current.availableRooms < roomCount) { throw new InsufficientInventoryError(/*...*/); } // Reserve with optimistic locking await this.inventoryRepository.atomicReserve(/* params */); }); return; // Success } catch (error) { if (error instanceof ConcurrentModificationError && attempt < MAX_RETRIES) { // Another booking happened simultaneously - retry this.logger.warn(`Concurrent modification on attempt ${attempt}, retrying`); await this.delay(100 * attempt); // Exponential backoff continue; } throw error; } } }}We've traced complete end-to-end flows through the Hotel Booking System, seeing how all components work together.
The Design in Review:
This Hotel Booking System design demonstrates production-ready patterns:
This design can scale from a single boutique hotel to a global chain with thousands of properties.
You've completed the Hotel Booking System case study—from requirements analysis through entity design, pricing engine, availability management, pattern application, and complete workflow tracing. This comprehensive design serves as a blueprint for any similar booking or temporal inventory system.