Functional Requirements:
Non-Functional Requirements:
We use a two-step booking process to support the "10-minute hold" UX requirement, keeping the APIs strictly RESTful.
1. Search Available Rooms GET /v1/rooms?start_time=2026-03-02T14:00:00Z&end_time=2026-03-02T15:00:00Z&capacity=10&equipment=projector Returns a list of rooms that do not have any overlapping CONFIRMED or PENDING bookings for the requested time.
2. Reserve a Room (Initiate 10-Minute Hold) POST /v1/bookings/reserve
JSON
{
"roomId": "room-uuid-123",
"startTime": "2026-03-02T14:00:00Z",
"endTime": "2026-03-02T15:00:00Z"
}
Returns: 201 Created with a bookingId and a status of PENDING.
3. Confirm the Booking POST /v1/bookings/{booking_id}/confirm
JSON
{
"organizerId": "user-uuid-456",
"title": "Architecture Review",
"participantIds": ["user-1", "user-2"]
}
Returns: 200 OK with the status updated to CONFIRMED.
4. Cancel a Booking DELETE /v1/bookings/{booking_id}
1. Concurrency Control (Preventing Double Bookings) Instead of relying on application-level locks or distributed Redis TTLs, we push the concurrency logic down to the database using PostgreSQL Exclusion Constraints.
When the Booking Service attempts to insert a new reservation, PostgreSQL evaluates the proposed time range against existing records for that specific roomId. If another transaction is currently committing an overlapping time block, PostgreSQL serializes the requests. The winner gets the row inserted; the loser receives a constraint violation error, which the API translates to an HTTP 409 Conflict ("Room is no longer available").
2. The Reservation State Machine (The 10-Minute Hold) The system manages inventory availability through states rather than physical locks:
/reserve API is called, a row is inserted with status = 'PENDING' and an expiresAt timestamp set to NOW() + 10 minutes. Because the row exists in the DB, the Exclusion Constraint prevents anyone else from booking this slot./confirm API is called, the system verifies expiresAt > NOW(). If valid, it updates the status to CONFIRMED and clears the expiresAt field.DELETE FROM Bookings WHERE status = 'PENDING' AND expiresAt < NOW(). This instantly frees up abandoned holds.3. Asynchronous Notifications To ensure the Booking Service remains fast, it does not send emails directly. Upon a successful transaction commit for a CONFIRMED booking, the service publishes a BookingConfirmedEvent to a message broker. The Notification Service consumes this event, constructs the email template, and sends it. If the email provider is down, the broker retains the message, ensuring eventual delivery without failing the user's booking request.