reservation-service
Bounded Context: Reservations (Core) · Owner: PMS · Phase: 0 · Storage: Cloud SQL Postgres (shared schema + RLS) + transactional outbox · Bundle: services/reservation-service/
reservation-service is the authoritative aggregate root for a hotel booking on Ghasi Melmastoon. It owns the Reservation lifecycle (quoted → held → confirmed → checked_in → in_house → checked_out, plus terminal states cancelled, no_show, expired_hold), guest details, dates, room/rate selection, special requests, modification audit, and — most importantly — it is the booking-saga orchestrator across inventory-service, pricing-service, payment-gateway-service, lock-integration-service, and notification-service.
The service does not own availability allocation (delegated to inventory-service), price calculation (pricing-service), money capture (payment-gateway-service), key issuance (lock-integration-service), or housekeeping tasks (housekeeping-service). It emits the events those services react to, and it consumes the events they emit, weaving them into a single saga state machine on the Reservation aggregate.
Purpose
- Be the single authoritative aggregate for everything a guest agreed to: who, where, when, which rooms, which rate, which special requests, which payment method, which channel.
- Orchestrate the booking saga end-to-end with declared compensation paths — never a synchronous chain longer than 2 hops (§7 of 02-enterprise-architecture).
- Provide a stable state machine with explicit guards, so every other service can subscribe to the events it cares about without ever reading our DB.
- Replicate active and upcoming reservations to the Electron desktop SQLite under a strict per-aggregate conflict policy:
server_authoritativefor state and money-related fields,lww+difffor free-text special requests and notes,append_onlyfor the modification audit trail.
Key responsibilities
- Reservation lifecycle —
quoted → held (10 min hold while payment is collected) → confirmed → checked_in → in_house → checked_out, with first-class cancellable and modifiable transitions, idempotent commands, and OCC-protected concurrency. - Booking-saga orchestration — coordinate
inventory-service(allocate),pricing-service(final quote with FX snapshot),payment-gateway-service(intent + capture or pending-cash),lock-integration-service(issue/update/revoke key), andnotification-service(multi-language confirmation, pre-arrival, post-stay). - Modifications with sub-types —
date_change,room_change,room_added,room_removed,guest_count_change,rate_change,guest_profile_update,special_request_added. Every modification is a Reservation event with full audit (who, when, before/after). - Walk-in bookings — single combined flow that creates and check-ins the reservation in one transaction, with deferred guest KYC for phone-by-staff.
- Group bookings — multi-room single-payer reservations with per-room sub-aggregates (
ReservationItem), partial-cancel support, and a single folio reference. - No-show, early-checkout, over-stay — policy-driven transitions that emit the events
billing-serviceneeds to settle the folio. - Reservation merging when a guest splits one stay across rooms, and group split when a group booking needs to fan into independent reservations.
- Multi-channel attribution — preserve the source channel (direct, meta, walk-in, phone-by-staff, OTA in Phase 3+) for revenue analysis and partner reconciliation.
- Cash-on-arrival as a first-class payment selection — the reservation can confirm with
payment_pending_cashand the folio captures the cash event later. - FX snapshot — for Iran-tenant reservations using IRR (and other multi-currency reservations), snapshot the FX rate at confirm-time and persist it on the reservation; never re-quote.
Aggregates owned
| Aggregate | Cardinality per Reservation | Purpose |
|---|---|---|
Reservation | 1 (root) | The authoritative booking; carries state machine, totals, channel, payment summary, FX snapshot |
ReservationItem | 1..N (one per assigned room per stay-day-window) | Room assignment + nightly rate breakdown + occupant link |
Guest | 1 (primary contact) | Booker; carries identity, locale, preferences, contact channels |
AdditionalGuest | 0..N | Other occupants, optional ID where required |
SpecialRequest | 0..N | Free-text + structured tags (halal, vegetarian, baby-cot, late check-in, …); resolved by notification-service and surfaced to backoffice |
ReservationModification | 0..N (audit) | Append-only audit row per modification: actor, type, before, after, occurredAt |
ReservationHold | 0..1 | Short-lived TTL hold record while payment is in flight |
Key APIs (REST, /api/v1/reservations)
| Method | Path | Purpose |
|---|---|---|
POST | /quotes | Request a quote (price + availability snapshot, no commitment) |
POST | /holds | Place a 10-minute hold (engages saga step 1) |
POST | / | Create + confirm (after payment) |
GET | /:id | Read full reservation (tenant-scoped) |
PATCH | /:id | Modify (with sub-type discriminator) |
POST | /:id/cancel | Cancel (policy-checked) |
POST | /:id/check-in | Check-in (issues key via lock-integration-service) |
POST | /:id/check-out | Check-out (closes folio, revokes key) |
POST | /:id/walk-in | Combined create+check-in for walk-ins |
POST | /:id/no-show | Declare no-show (policy applied) |
POST | /:id/special-requests | Append a special request |
GET | /by-guest/:guestId | List a guest's stays |
POST | /groups/:groupId/split | Split a group booking |
POST | /merge | Merge two reservations |
Consumed by bff-tenant-booking-service (guest funnel) and bff-backoffice-service (front-desk staff). Never consumed directly by bff-consumer-service (the meta layer reads via search-aggregation-service).
Key events published
| Event | Trigger |
|---|---|
melmastoon.reservation.quote.requested.v1 | A guest or staff asks for a price |
melmastoon.reservation.quote.created.v1 | Quote computed and pinned with TTL |
melmastoon.reservation.held.v1 | Inventory hold placed (TTL 10 min) |
melmastoon.reservation.hold_expired.v1 | Hold TTL elapsed without confirm |
melmastoon.reservation.confirmed.v1 | Reservation confirmed (post-capture or pending-cash) |
melmastoon.reservation.cancelled.v1 | Cancellation accepted (policy-checked) |
melmastoon.reservation.modified.v1 | Any non-date modification |
melmastoon.reservation.dates_changed.v1 | Stay window mutated (drives re-allocation + key update) |
melmastoon.reservation.check_in_started.v1 | Front-desk staff initiated check-in |
melmastoon.reservation.checked_in.v1 | Check-in complete (folio opened, key issued) |
melmastoon.reservation.checkout_started.v1 | Check-out initiated |
melmastoon.reservation.checked_out.v1 | Check-out complete (folio closed, key revoked) |
melmastoon.reservation.no_show.v1 | No-show declared after grace window |
melmastoon.reservation.early_checkout.v1 | Guest left before scheduled check-out |
melmastoon.reservation.overstayed.v1 | Guest still in-house after scheduled check-out + grace |
melmastoon.reservation.special_request.added.v1 | A new special request appended |
Key events consumed
| Event | Effect |
|---|---|
melmastoon.inventory.allocation.committed.v1 | Saga step 1 success → advance to payment step |
melmastoon.inventory.allocation.failed.v1 | Compensation: cancel held reservation, refund pending payment if any |
melmastoon.payment.transaction.captured.v1 | Advance from held → confirmed; trigger key issuance for stays starting today |
melmastoon.payment.transaction.failed.v1 | Compensation: cancel held reservation, release inventory hold |
melmastoon.payment.transaction.refunded.v1 | Record refund on reservation (informational; folio is the ledger) |
melmastoon.lock_integration.key_credential.issued.v1 | Link credential id to reservation; surface in backoffice + send to guest |
melmastoon.lock_integration.key_credential.failed.v1 | Alert front desk; allow manual key with requires_manual_key=true flag |
melmastoon.tenant.settings.changed.v1 | Refresh hold-window default, cancellation policy version, default channel attribution |
melmastoon.property.room.taken_out_of_order.v1 | Re-accommodate active reservations on the affected room |
Upstream / downstream
Upstream (we consume): inventory-service, pricing-service, payment-gateway-service, lock-integration-service, tenant-service, property-service.
Downstream (we publish for): inventory-service, billing-service, lock-integration-service, housekeeping-service, notification-service, analytics-service, audit-service, search-aggregation-service, sync-service, bff-backoffice-service, bff-tenant-booking-service.
Non-functional requirements
| NFR | Target |
|---|---|
| Booking-saga p99 (held → confirmed) | < 5 s including external payment capture |
| Hold-expiry job lag | < 30 s beyond TTL |
| Modification success rate | > 99.5% monthly |
| API availability | 99.95% monthly |
| Tenant isolation | RLS-enforced; tenant-isolation.spec.ts mandatory in CI |
| Sync footprint | Active + upcoming (next 30 days) + recent history (last 60 days) replicated to desktop SQLite |
| Replicas | Min 3 Cloud Run instances (hot path); separate hold-expiry worker as Cloud Run cron job (every 30 s) |
Where to go next
- Implementation-grade detail:
services/reservation-service/SERVICE_OVERVIEW.mdand the rest of the 17-doc bundle. - Saga participants and compensation paths:
docs/04-event-driven-architecture.md§7. - Conflict policy table for the desktop sync engine:
docs/02-enterprise-architecture.md§8.2. - Booking funnel UX contract:
docs/frontend/01-web-and-mobile-specification.md. - Front-desk operational flow:
docs/journeys/01-core-user-journeys.md.