bff-backoffice-service
Backend-for-Frontend for the Electron desktop backoffice application used by hotel staff (front desk, housekeeping, maintenance, F&B managers, GMs). This BFF is a stateless orchestration layer over our domain services. It owns no domain state, makes no domain mutations directly, and emits no domain events. When the desktop is offline, it bypasses this BFF entirely and reads/writes its local SQLite store; this BFF is only on the path when the desktop is online.
| Field | Value |
|---|---|
| Owner | Frontend Platform team |
| Scope | Single bounded context: backoffice desktop session orchestration |
| Stage | Phase 1 (greenfield) |
| Service ID | bff-backoffice-service |
| Class | BFF (no domain state, stateless on hot path) |
| Naming root | MELMASTOON.BFF.BACKOFFICE.* |
| Event subjects | melmastoon.bff.backoffice.* |
| Database scope | Postgres for outbox + idempotency + sync-cursor cache + activity ledger; Memorystore for sessions + dashboard cache |
1. Purpose
The desktop client is offline-first (ADR-0003) — when the network is up, it talks to this BFF for: dashboard composition, workbench reads, sync handshake (proxy to sync-service), AI suggestion fetch (proxy to ai-orchestrator-service), lock-action proxying (to lock-integration-service), reservation/folio mutations (to domain services with idempotency keys), device session lifecycle, alert acknowledgment, and operator preference sync. When the network is down, the desktop talks to its local SQLite and queues outbox writes; on reconnect it drains via this BFF (more precisely, via this BFF for non-sync-protocol calls, and directly to sync-service for the bulk pull/push protocol — this BFF orchestrates the cursor handshake but does not proxy bulk binary streams).
2. Why a BFF (not direct-to-service)
- The desktop renders ~30 widgets per dashboard pull. A direct fanout from the renderer to 8 services over potentially flaky links is wasteful. The BFF composes one chunked SSR stream.
- Device-bound JWT lifecycle (
iam-service.refreshwith device proof-of-possession) is centralized here, not duplicated across the desktop and three other client surfaces. - AI suggestion polling/SSE multiplexing keeps the desktop from holding a long connection per suggestion class.
- Telemetry shape (operator activity, dashboard view, sync-cursor advance, device heartbeat) is BFF-owned, normalized across staff workstations.
- Lock-action proxying carries audit context (
operator_id,device_id,reservation_id,mfa_attestation) that the desktop should not need to assemble.
3. Aggregates owned (session/projection only)
| Aggregate | Storage | Note |
|---|---|---|
BackofficeSession | Memorystore (TTL 12 h) | Operator + device + tenant + property scope |
DashboardSnapshot | Memorystore (TTL 30 s) | Composed dashboard payload, per (tenantId, propertyId, operatorRole) |
WorkbenchView | Memorystore (TTL 15 s) | Per-feature view: front-desk grid, housekeeping board, maintenance board |
AISuggestionInbox | Memorystore + Postgres lightweight log | Mirror of orchestrator inbox; decisions logged for audit |
AlertInbox | Memorystore + Postgres ack log | Operator-acknowledged alerts |
OperatorActivity | Postgres ledger (90 d) | Operator action telemetry |
DeviceSyncStatus | Postgres + Memorystore | Per-device cursor cache + heartbeat |
OperatorPreferences | Memorystore (read-through) + Postgres mirror | Layout, density, columns, shortcuts |
KeyboardShortcutMap | Static + Postgres overrides | Server-rendered map for the desktop renderer |
OfflineActionQueueHint | Postgres (informational only) | Hint of what desktop queued; desktop owns the canonical queue locally |
The BFF holds no reservation, folio, lock, room, rate, inventory, or guest data. Those live in their respective services.
4. Hot endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /bff/backoffice/v1/dashboard?propertyId | Composed dashboard SSR payload |
GET | /bff/backoffice/v1/today?propertyId | Quick "today at a glance" panel |
GET | /bff/backoffice/v1/arrivals?propertyId&date | Today's expected arrivals |
GET | /bff/backoffice/v1/departures?propertyId&date | Today's expected departures |
GET | /bff/backoffice/v1/in-house?propertyId | In-house guests |
GET | /bff/backoffice/v1/housekeeping/board?propertyId | Housekeeping board view |
GET | /bff/backoffice/v1/maintenance/board?propertyId | Maintenance board view |
GET | /bff/backoffice/v1/ai/suggestions?propertyId&category | AI suggestion list |
POST | /bff/backoffice/v1/ai/suggestions/{id}/decide | Decide a suggestion (accept/reject/modify) |
GET | /bff/backoffice/v1/alerts?propertyId&filter | Alert inbox |
POST | /bff/backoffice/v1/alerts/{id}/acknowledge | Acknowledge an alert |
PUT | /bff/backoffice/v1/preferences | Update operator preferences |
POST | /bff/backoffice/v1/devices/{deviceId}/heartbeat | Device heartbeat (every 60 s) |
GET | /bff/backoffice/v1/sync/cursor?deviceId | Read device's last-known sync cursor |
POST | /bff/backoffice/v1/sync/cursor | Advance device's cursor (post-pull) |
POST | /bff/backoffice/v1/sync/handshake | Negotiate a sync session with sync-service; returns sync-session-token |
POST | /bff/backoffice/v1/locks/{reservationId}/issue-key | Proxy to lock-integration-service.issueKey |
POST | /bff/backoffice/v1/locks/{reservationId}/revoke-key | Proxy to lock-integration-service.revokeKey |
GET | /bff/backoffice/v1/sse/stream?propertyId&channels | SSE stream for AI suggestions, alerts, dashboard refresh hints |
POST | /bff/backoffice/v1/auth/refresh | Device-bound JWT refresh (proxy to iam-service) |
POST | /bff/backoffice/v1/auth/sign-out | Sign out device |
5. Top events published
melmastoon.bff.backoffice.dashboard.viewed.v1, .workbench.opened.v1, .ai_suggestion.decided.v1, .alert.acknowledged.v1, .operator.activity.v1, .device.heartbeat.v1, .sync.cursor_advanced.v1, .sync.handshake_completed.v1, .session.opened.v1, .session.closed.v1, .lock.action_proxied.v1. All telemetry; never domain.
6. Top events consumed
| Subject | Purpose |
|---|---|
melmastoon.iam.session.revoked.v1 | Force re-login on affected devices; broadcast on SSE |
melmastoon.iam.user.disabled.v1 | Tear down sessions for disabled operators |
melmastoon.reservation.* (selected) | Project into dashboard cache hints |
melmastoon.housekeeping.task.created.v1 | Project into housekeeping board hints |
melmastoon.maintenance.workorder.opened.v1 | Project into maintenance board hints |
melmastoon.ai.suggestion.created.v1 | Update AISuggestionInbox cache |
melmastoon.ai.suggestion.invalidated.v1 | Drop from inbox |
melmastoon.alert.raised.v1 | Insert into AlertInbox; push via SSE |
melmastoon.alert.resolved.v1 | Drop from inbox; push via SSE |
melmastoon.theme.published.v1 | Invalidate any rendered shortcut help (if branded) |
Events are projected into Memorystore caches for fast dashboard reads; they are not persisted as the source of truth.
7. Upstream services (composes from)
iam-service, tenant-service, property-service, reservation-service, inventory-service, pricing-service, housekeeping-service, maintenance-service, billing-service, lock-integration-service, ai-orchestrator-service, notification-service (alert metadata), sync-service (handshake + cursor), analytics-service (KPI snapshots).
8. Downstream consumers
The Electron desktop application (@ghasi/app-desktop-backoffice) is the only consumer. The narrow contextBridge API surface (window.melmastoon.*) calls REST endpoints listed in §4 from the renderer through the main process.
9. Non-functional
| Concern | Target |
|---|---|
/dashboard p95 (warm) | < 600 ms |
/dashboard first-byte (chunked SSR) | < 200 ms |
| Workbench p95 | < 400 ms |
| Lock-action p95 | < 800 ms (orchestrator-bounded) |
| Heartbeat p95 | < 80 ms |
| Sync handshake p95 | < 300 ms |
| Availability | 99.9% (vs 99.95% for guest-facing BFFs — desktop tolerates BFF being briefly down via offline mode) |
| Concurrent operators per property | 50 (peak) |
| Devices per tenant | 200 (peak) |
10. Cross-references
- services/bff-backoffice-service/SERVICE_OVERVIEW.md
- services/bff-backoffice-service/API_CONTRACTS.md
- services/bff-backoffice-service/SYNC_CONTRACT.md
- services/bff-backoffice-service/DEPLOYMENT_TOPOLOGY.md
- docs/architecture/ADR-0003-electron-offline-first-desktop.md
- docs/02-enterprise-architecture.md §8 Sync & Offline Architecture