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:
- 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. - 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. - 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-serviceandpayment-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-servicedecides what (if any) email/SMS to send. - Backoffice / staff functions: lives in
bff-backoffice-service.
3. Aggregates owned
| Aggregate | Cardinality | Purpose | Identity prefix | Storage |
|---|---|---|---|---|
TenantBootstrap (cache) | per tenant | Composed bootstrap blob | (composite) | Memorystore (Redis), TTL 5 min, invalidated by event |
BookingDraft | per bdr_ | Short-lived booking draft | bdr_ | Memorystore (Redis), TTL 30 min + cold mirror in Postgres |
BookingHandoffArrival | short-lived | Verified inbound handoff token, single-use | bha_ | Postgres handoff_arrival_log (TTL 30 min) |
MarketingAttribution | per session | UTM + handoff campaign attribution | (n/a) | Memorystore session blob |
LoyaltyContext (Phase 2+) | per session | Loyalty hint to pricing | (n/a) | Memorystore |
ConfirmationView | per reservationId | Composed post-confirm view-model | (composite) | Memorystore (TTL 5 min, regenerated on demand) |
BookingDraftSnapshot | per bdr_ | Cold mirror for abandoned-cart + analytics | bds_ | 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)
- Slug resolution + tenant guard — every request first resolves
tenantSlug(or custom domain) →tenantIdviatenant-servicewith cache TTL 1 h. Suspended or unknown tenants short-circuit with explicit error codes. - Bootstrap composition —
GET /bootstrapcomposes{ tenant, theme, flowConfig, locales, currencies, paymentMethods, policies, handoffPayload? }fromtenant-service,theme-config-service, and (when present) the inbound handoff token. Embeds CSP nonce. - Availability composition —
GET /availabilityfans out:inventory-service(allocations),property-service(room types + photos),pricing-service(cheapest rate per room type for stay window). Returns within deadline; missing prices markedpriceUnavailable=true. - Quote —
POST /quoteproxies topricing-service; persistsquote.id+quote.expiresAton theBookingDraft. - Hold —
POST /holdproxies toreservation-service; createsbdr_and persists draft state. Quote TTL drives hold TTL. - Guest-details capture —
PATCH /draft/{id}validates and persists guest fields onto the draft. No write toreservation-serviceuntil/confirm. - Payment intent —
POST /draft/{id}/payment-intentcallspayment-gateway-service; receives provider-specific intent + redirect URL; stores intent reference on draft. - Return + confirm —
POST /draft/{id}/returnhandles the redirect-return; verifies state withpayment-gateway-service; on success callsreservation-service /confirmwith idempotency key derived from(draftId, reservationToken). - Confirmation view —
GET /confirmation/{reservationId}composes{ reservation, folioSummary, keyCredentialPlaceholder, postStayInfo, support }fromreservation-service,billing-service,lock-integration-service(placeholder only — issuance happens later),tenant-service. - Handoff consumption —
POST /handoff/consumeverifies HMAC token frombff-consumer-service, primes draft with handoff payload, marks token consumed in ledger. - Telemetry — emit
melmastoon.bff.tenant.*events through Postgres outbox per 04 §3. No domain events. - Locale + currency — accept
Accept-LanguageandX-Currencyheaders; resolve fallback chain; persist on session; on currency change emitcurrency.changed.v1and re-quote on next quote call. - Themed errors — every error response includes
themeContext = { palette, locale, brandName }so the client renders branded error UI. - 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-serviceconsumes our funnel events and decides.
6. Upstream services consumed
| Service | Why | Cache TTL | Failure handling |
|---|---|---|---|
tenant-service | Slug → tenantId; suspension status | 1 h (event-invalidated) | 1× retry; 502 on persistent failure |
theme-config-service | Bootstrap theme + flow config | 5 min (event-invalidated) | Serve last-good cache up to 30 min stale; 503 if no cache |
property-service | Room types + photos + policies | 5 min | Stale cache up to 30 min |
inventory-service | Availability per date | 30 s | Stale cache up to 60 s with stale=true |
pricing-service | Quote + cheapest rate | none for quote; 60 s for cheapest | Stale cache for cheapest; for quote 502 |
reservation-service | Hold + confirm | none | 502 / 504 propagated; idempotency key absorbs retries |
payment-gateway-service | Intent + return | none | 502 / 504 propagated |
lock-integration-service | Confirmation page placeholder | 60 s | Soft-fail: placeholder shown |
iam-service (Phase 2+) | Loyalty context, optional auth | 30 s | Soft-fail: anonymous flow |
bff-consumer-service | Handoff token HMAC verification (shared key) | n/a (verification, not network) | n/a |
7. Downstream consumers
analytics-service(consumesbff.tenant.*for funnel + conversion attribution).notification-service(consumesdraft.converted.v1,draft.abandoned.v1for confirmation + abandoned-cart).audit-service(consumeshandoff.consumed.v1,flow.error_encountered.v1).- The web + mobile tenant booking apps.
8. Surfaces
| Surface | Channel | Notes |
|---|---|---|
| Web | HTTPS | https://{tenantSlug}.melmastoon.ghasi.io/api/... and custom domains via SNI cert mapping |
| Mobile (RN) | HTTPS | Same base URL; carries X-Client-Surface: mobile-tenant |
| Internal | Internal HTTP | https://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-srcfor 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
| NFR | Target |
|---|---|
/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 |
| Availability | 99.95% monthly |
| Cache hit ratio | bootstrap ≥ 95%; availability ≥ 50% |
| Replicas | min 3 / region; auto-scale to 30 on flash sale |
| Error budget consumption | < 5% per 28-day rolling window |
| BookingDraft TTL | 30 min |
12. What changes between Phase 1 and Phase 2
| Capability | Phase 1 | Phase 2 |
|---|---|---|
| Authentication | Anonymous only | Optional guest sign-in via iam-service |
| Loyalty | Not present | LoyaltyContext propagated to pricing |
| Abandoned cart | Telemetry only | notification-service triggers email/SMS |
| Custom domain | Subdomain only | Full custom-domain support via ADR-0006 |
| Multi-currency display | Read-only conversion | Real-time FX with provider rate comparison |
| Server-side rendering | None | SSR for first paint via Cloud Run |
13. Glossary
| Term | Meaning |
|---|---|
| Tenant slug | Public identifier in the URL (kabul-grand-hotel.melmastoon.ghasi.io) |
| Bootstrap | The single first call that returns everything needed to render chrome before any data fetch |
| Booking draft | Server-side, short-lived blob that mirrors UI state for resilience across page refresh |
| Flow state | Finite-state machine of the booking funnel |
| Handoff | Signed token minted by bff-consumer-service that carries pre-populated booking context |
| Payment intent | The provider-specific object that holds the funds-capture lifecycle reference |
| Return | The redirect-back URL after the payment provider's flow |
| Confirmation view | The composed post-confirm page that combines reservation + folio + key + post-stay info |