Distributed Hotel Booking Platform

The Engineering Challenge

Building a reliable booking engine is fundamentally a problem of distributed state and concurrency. When hundreds of users compete for the same limited room inventory, the system must guarantee ACID compliance, prevent double bookings, and maintain low latency.

For this platform, I architected a polyglot microservices ecosystem from scratch. Rather than relying on a monolithic framework, I designed five isolated domain services (Auth, Booking, Hotel, Review, and Notification) using Node.js (TypeScript) and Go, communicating via synchronous REST for strict validation and asynchronous Redis queues for non-blocking operations.

Architectural Philosophy: "Share-Nothing"

Each service operates in a completely independent directory with its own dependencies, its own environmental configuration, and its own isolated MySQL database.

While this approach requires duplicating lightweight assets (like custom error classes and DTOs), it prevents the "distributed monolith" anti-pattern. A change in the Booking Service's database schema or error handling has zero risk of accidentally breaking the Hotel Service.

Microservices architecture diagram of a hotel booking backend showing Auth, Hotel, Booking, Review, and Notification services with MySQL databases and Redis for distributed locking and asynchronous processing.
Microservices architecture with isolated databases, Redis, and asynchronous processing.

Solving Core Technical Problems

1. Concurrency Control: The Double-Booking Problem

To guarantee idempotency during the checkout flow, I implemented a two-tier locking strategy that handles both distributed traffic spikes and row-level data integrity.

  • Layer 1: Distributed Locking (Redis Redlock): When a booking is initiated, the system acquires a distributed lock via Redlock scoped to the booking hotelId with a TTL. This acts as a coarse-grained traffic filter, preventing a stampede of concurrent requests for the same property from overwhelming the database simultaneously.
  • Layer 2: Pessimistic Row-Level Locking (MySQL): During the actual payment confirmation phase, the Booking Service utilises a strict pessimistic lock within a Prisma $transaction (on the idempotency key). This guarantees that even if a client retries a network request, the database serialises the operations, making double-billing or double-booking impossible.
Hotel booking sequence showing availability checks, Redlock locking, idempotency handling, and transactional booking confirmation.
Booking flow with distributed locking and idempotent confirmation.

2. Eventual Consistency & Asynchronous Workers

Synchronous third-party API calls (like SMTP email dispatch) degrade primary API response times. To solve this, the Notification Service acts as an isolated background worker.

When a user registers or confirms a booking, the Auth/Booking services push a MailPayload to a BullMQ (Redis) queue and immediately return a 200 OK to the client. The Notification Service consumes these events, injects dynamic data into Handlebars (.hbs) templates, and dispatches the emails. If the queue push fails, the system logs the error and gracefully degrades rather than blocking the user's critical path.

3. Data Integrity & Anti-Spoofing (Review Service)

In a distributed system, foreign keys cannot enforce relationships across different databases. To ensure users cannot submit fraudulent reviews for hotels they never visited, the Go-based Review Service employs a strict Anti-Spoofing Gateway Pattern.

When a review payload is received, the service completely ignores the client-provided userId and hotelId. Instead, it makes a synchronous backend HTTP call to the Booking Service using the booking_id. If the booking is valid, it extracts the authoritative IDs directly from the Booking Service response, effectively neutralizing malicious payload injection.

Furthermore, to maintain high throughput, the Review Service writes the review to its local DB and flags it with is_synced: false, scaffolding an Outbox Pattern for eventual consistency aggregation with the Hotel Service.

Engineering Retrospective & Next Steps

Building this project provided valuable hands-on experience in managing microservice boundaries and balancing complexity with developer velocity. Practical next steps for the codebase include:

  • Containerization: Wrapping all services into a unified docker-compose setup to simplify local setup and service orchestration.
  • Observability & Health Checks: Adding centralized log monitoring and standard /health endpoints to streamline debugging and monitoring.