Skip to main content

SERVICE_OVERVIEW — bff-tenant-booking-service

Bundle index: SERVICE_OVERVIEW · DOMAIN_MODEL · APPLICATION_LOGIC · API_CONTRACTS · EVENT_SCHEMAS · DATA_MODEL · SYNC_CONTRACT · AI_INTEGRATION · SECURITY_MODEL · OBSERVABILITY · TESTING_STRATEGY · DEPLOYMENT_TOPOLOGY · FAILURE_MODES · LOCAL_DEV_SETUP · SERVICE_READINESS · SERVICE_RISK_REGISTER · MIGRATION_PLAN

Strategic anchors: 02 Enterprise Architecture §5 · 04 Event-Driven Architecture · 05 API Design §9.2 · 06 Data Models · 07 Security/Compliance/Tenancy · 10 Payments Architecture · Standards · NAMING · Standards · ERROR_CODES

1. Purpose

bff-tenant-booking-service is the Backend-for-Frontend that powers the tenant-branded booking experience of Ghasi Melmastoon — the responsive web app and React Native mobile app served under each tenant's domain (https://{tenantSlug}.melmastoon.ghasi.io, optional custom domain https://booking.<tenant>.com). A single shared client codebase (@ghasi/app-web-tenant-booking, @ghasi/app-mobile-tenant-booking) is themed per tenant at runtime via the bootstrap call to this BFF.

It exists to answer one question:

Where does a tenant-scoped guest land, get themed, get composed, get held, get charged, and get confirmed — without leaking domain logic or money rails into the client?

Three properties make this BFF necessary and irreducible:

  1. Tenant scope as the first-class concern. Every request is keyed on (tenantSlug | custom domain); every response is tenant-scoped; the BFF rejects any unresolvable tenant. The consumer BFF (cross-tenant) and backoffice BFF (staff-scope) cannot serve this surface without breaking their own contracts.
  2. Money + state-machine orchestration. Booking is a multi-step saga (search → quote → hold → guest details → payment intent → return → confirm). The BFF orchestrates the client-visible version of that saga while domain services (reservation-service, payment-gateway-service) own the authoritative state. A thinner gateway would force the client to fan out to 5+ services and leak rails.
  3. Theming as a hot path. Tenants demand pixel-level brand control. Bootstrap must compose theme + flow config + locales + currencies + payment methods + policies in a single round trip and ship a CSS sheet of design tokens for first-paint. Folding this into the consumer BFF would either inflate cross-tenant payloads or scatter theme logic across surfaces.

This service owns no domain state, performs no domain mutations, and emits no domain events. Its only writes are short-lived BookingDraft blobs in Memorystore, telemetry rows in Postgres outbox, draft snapshots for abandoned-cart, idempotency keys, and a handoff-arrival ledger.

2. Bounded context

Context name: BFF · Tenant Booking Domain class: Supporting (the differentiator is the saga orchestration discipline + theming bootstrap, not the booking domain itself) Ubiquitous language: TenantBootstrap, BookingDraft, BookingFlowState (searching | selecting | quoting | holding | collecting_details | paying | confirming | confirmed | failed | abandoned), PaymentSelection, GuestProfile (booking-time), LoyaltyContext (Phase 2+), MarketingAttribution, BookingHandoffArrival, ConfirmationView, AvailabilitySnapshot, RoomCardVM, QuoteVM, PaymentMethodVM, PolicyVM, ThemeBundle, FlowConfig.

What is in:

  • View-model composition for /bootstrap, /availability, /quote, /hold, /draft/{id}, /payment-intent, /return, /confirm, /confirmation/{reservationId}, /policies, /handoff/consume.
  • Tenant slug → tenantId resolution with cache.
  • Booking draft lifecycle (Memorystore + cold mirror).
  • Payment redirect dance: outbound URL holding, return verification, idempotent confirm.
  • Conversion-funnel telemetry emission.
  • Theme bundle composition + design-token CSS sheet for first-paint.
  • Locale + display-currency state.

What is out:

  • Domain mutations: hold, confirm, charge, refund all live in reservation-service and payment-gateway-service.
  • Theme authoring: lives in theme-config-service; this BFF reads only.
  • Cross-tenant search: lives in bff-consumer-service. Guest arrives via signed handoff or direct tenant link.
  • Authenticated guest accounts (Phase 2+) and loyalty programs.
  • Notification delivery: this BFF emits booking.draft.converted.v1; notification-service decides what (if any) email/SMS to send.
  • Backoffice / staff functions: lives in bff-backoffice-service.

3. Aggregates owned

AggregateCardinalityPurposeIdentity prefixStorage
TenantBootstrap (cache)per tenantComposed bootstrap blob(composite)Memorystore (Redis), TTL 5 min, invalidated by event
BookingDraftper bdr_Short-lived booking draftbdr_Memorystore (Redis), TTL 30 min + cold mirror in Postgres
BookingHandoffArrivalshort-livedVerified inbound handoff token, single-usebha_Postgres handoff_arrival_log (TTL 30 min)
MarketingAttributionper sessionUTM + handoff campaign attribution(n/a)Memorystore session blob
LoyaltyContext (Phase 2+)per sessionLoyalty hint to pricing(n/a)Memorystore
ConfirmationViewper reservationIdComposed post-confirm view-model(composite)Memorystore (TTL 5 min, regenerated on demand)
BookingDraftSnapshotper bdr_Cold mirror for abandoned-cart + analyticsbds_Postgres booking_draft_snapshots (TTL 30 d)

tenantId is always non-null on this BFF. The hot session blob is small (< 4 KiB); larger payloads (selected room photos, policies) live in cache keyed by tenant.

4. Responsibilities (numbered)

  1. Slug resolution + tenant guard — every request first resolves tenantSlug (or custom domain) → tenantId via tenant-service with cache TTL 1 h. Suspended or unknown tenants short-circuit with explicit error codes.
  2. Bootstrap compositionGET /bootstrap composes { tenant, theme, flowConfig, locales, currencies, paymentMethods, policies, handoffPayload? } from tenant-service, theme-config-service, and (when present) the inbound handoff token. Embeds CSP nonce.
  3. Availability compositionGET /availability fans out: inventory-service (allocations), property-service (room types + photos), pricing-service (cheapest rate per room type for stay window). Returns within deadline; missing prices marked priceUnavailable=true.
  4. QuotePOST /quote proxies to pricing-service; persists quote.id + quote.expiresAt on the BookingDraft.
  5. HoldPOST /hold proxies to reservation-service; creates bdr_ and persists draft state. Quote TTL drives hold TTL.
  6. Guest-details capturePATCH /draft/{id} validates and persists guest fields onto the draft. No write to reservation-service until /confirm.
  7. Payment intentPOST /draft/{id}/payment-intent calls payment-gateway-service; receives provider-specific intent + redirect URL; stores intent reference on draft.
  8. Return + confirmPOST /draft/{id}/return handles the redirect-return; verifies state with payment-gateway-service; on success calls reservation-service /confirm with idempotency key derived from (draftId, reservationToken).
  9. Confirmation viewGET /confirmation/{reservationId} composes { reservation, folioSummary, keyCredentialPlaceholder, postStayInfo, support } from reservation-service, billing-service, lock-integration-service (placeholder only — issuance happens later), tenant-service.
  10. Handoff consumptionPOST /handoff/consume verifies HMAC token from bff-consumer-service, primes draft with handoff payload, marks token consumed in ledger.
  11. Telemetry — emit melmastoon.bff.tenant.* events through Postgres outbox per 04 §3. No domain events.
  12. Locale + currency — accept Accept-Language and X-Currency headers; resolve fallback chain; persist on session; on currency change emit currency.changed.v1 and re-quote on next quote call.
  13. Themed errors — every error response includes themeContext = { palette, locale, brandName } so the client renders branded error UI.
  14. Idempotency — every mutating endpoint accepts X-Idempotency-Key (ULID); duplicate writes return cached response within 24 h.

5. Out-of-scope responsibilities

  • The booking domain itself — this BFF never decides whether a hold is granted or whether overbooking should be allowed; that is reservation-service + inventory-service.
  • Money capture, refunds, webhook verification — that is payment-gateway-service.
  • Folio creation, tax computation, invoice issuance — that is billing-service.
  • Theme authoring or asset uploads — that is theme-config-service.
  • Email/SMS delivery — notification-service consumes our funnel events and decides.

6. Upstream services consumed

ServiceWhyCache TTLFailure handling
tenant-serviceSlug → tenantId; suspension status1 h (event-invalidated)1× retry; 502 on persistent failure
theme-config-serviceBootstrap theme + flow config5 min (event-invalidated)Serve last-good cache up to 30 min stale; 503 if no cache
property-serviceRoom types + photos + policies5 minStale cache up to 30 min
inventory-serviceAvailability per date30 sStale cache up to 60 s with stale=true
pricing-serviceQuote + cheapest ratenone for quote; 60 s for cheapestStale cache for cheapest; for quote 502
reservation-serviceHold + confirmnone502 / 504 propagated; idempotency key absorbs retries
payment-gateway-serviceIntent + returnnone502 / 504 propagated
lock-integration-serviceConfirmation page placeholder60 sSoft-fail: placeholder shown
iam-service (Phase 2+)Loyalty context, optional auth30 sSoft-fail: anonymous flow
bff-consumer-serviceHandoff token HMAC verification (shared key)n/a (verification, not network)n/a

7. Downstream consumers

  • analytics-service (consumes bff.tenant.* for funnel + conversion attribution).
  • notification-service (consumes draft.converted.v1, draft.abandoned.v1 for confirmation + abandoned-cart).
  • audit-service (consumes handoff.consumed.v1, flow.error_encountered.v1).
  • The web + mobile tenant booking apps.

8. Surfaces

SurfaceChannelNotes
WebHTTPShttps://{tenantSlug}.melmastoon.ghasi.io/api/... and custom domains via SNI cert mapping
Mobile (RN)HTTPSSame base URL; carries X-Client-Surface: mobile-tenant
InternalInternal HTTPhttps://bff-tenant.melmastoon.internal for BFF↔BFF handoff handshake

9. Key decisions (linked ADRs)

  • ADR-0001 Core architecture + tech stack — Node 20, NestJS, Cloud Run, GCP. This BFF inherits all defaults.
  • ADR-0002 Multi-tenancy model — slug → tenantId resolution canonicalized through tenant-service. Custom-domain support deferred to ADR-0006 (TBD).
  • ADR-0003 Electron offline-first desktop — explicitly out of scope for this BFF; we never serve the desktop.
  • ADR-0004 Lock integration abstraction — confirmation page consumes the abstracted shape.
  • ADR-005 (TBD) BFF orchestration discipline — codifies "BFFs hold no domain state and emit no domain events"; this BFF is the canonical reference implementation.

10. Out-of-band integrations

  • Payment gateway redirects — outbound to provider-hosted pages (PayPal, local MFS, card processor); the return URL lives on the BFF. CSP allows frame-src for the small set of providers that use embedded redirects.
  • Custom-domain TLS — handled by GCLB SNI mapping; tenants submit DNS CNAME and we provision via Cloud Certificate Manager.

11. Non-functional requirements

NFRTarget
/bootstrap p95< 350 ms warm; < 800 ms cold
/availability p95< 600 ms (3-way fanout)
/quote p95< 250 ms
/hold p95< 600 ms
/confirm p95< 1.2 s
Confirmation page first-byte< 800 ms
Availability99.95% monthly
Cache hit ratiobootstrap ≥ 95%; availability ≥ 50%
Replicasmin 3 / region; auto-scale to 30 on flash sale
Error budget consumption< 5% per 28-day rolling window
BookingDraft TTL30 min

12. What changes between Phase 1 and Phase 2

CapabilityPhase 1Phase 2
AuthenticationAnonymous onlyOptional guest sign-in via iam-service
LoyaltyNot presentLoyaltyContext propagated to pricing
Abandoned cartTelemetry onlynotification-service triggers email/SMS
Custom domainSubdomain onlyFull custom-domain support via ADR-0006
Multi-currency displayRead-only conversionReal-time FX with provider rate comparison
Server-side renderingNoneSSR for first paint via Cloud Run

13. Glossary

TermMeaning
Tenant slugPublic identifier in the URL (kabul-grand-hotel.melmastoon.ghasi.io)
BootstrapThe single first call that returns everything needed to render chrome before any data fetch
Booking draftServer-side, short-lived blob that mirrors UI state for resilience across page refresh
Flow stateFinite-state machine of the booking funnel
HandoffSigned token minted by bff-consumer-service that carries pre-populated booking context
Payment intentThe provider-specific object that holds the funds-capture lifecycle reference
ReturnThe redirect-back URL after the payment provider's flow
Confirmation viewThe composed post-confirm page that combines reservation + folio + key + post-stay info