Skip to main content

SERVICE_OVERVIEW — bff-backoffice-service

Sibling: DOMAIN_MODEL · APPLICATION_LOGIC · API_CONTRACTS · SYNC_CONTRACT · DEPLOYMENT_TOPOLOGY

Cross-cutting: 03-microservices/bff-backoffice-service · ADR-0003 Electron offline-first desktop · 02 Enterprise Architecture · §8 Sync & Offline

1. One-line purpose

A thin, stateless orchestration layer between the Electron desktop backoffice application and the platform's domain services. It composes dashboards, proxies authenticated mutations, brokers sync handshakes, multiplexes AI suggestion + alert streams, manages device-bound session lifecycle, and emits operator-activity telemetry — without owning domain state.

2. Problem it solves

The Electron desktop client is offline-first: when the network is healthy, it talks to the cloud; when the network is unreliable (the design assumption — see ADR-0003 §5), it falls back to local SQLite and queues mutations. The cloud-side surface that the desktop talks to has three jobs:

  1. Compose what would otherwise be 8–12 parallel cross-service calls per dashboard render — over potentially flaky network — into a single chunked SSR stream that the desktop can paint progressively even with high latency.
  2. Bridge Electron-specific lifecycle — device-bound JWT refresh with proof-of-possession, narrow contextBridge API contract on the desktop side, sync cursor handshake, AI suggestion delivery (poll-fallback for hostile networks; SSE preferred), and lock-action proxying with full audit envelope.
  3. Emit operator telemetry without making the desktop assemble the envelope (operator id, device id, tenant id, property id, role, locale, build version, network mode).

These three jobs would otherwise become 8 different per-feature concerns spread across the desktop renderer; centralizing them here keeps the desktop renderer pure-UI and lets all staff workstations share one composition contract.

3. Bounded context

In scopeOut of scope
Composed dashboard payloadsDashboard authoring
Workbench view composition (front desk, housekeeping, maintenance)Domain logic on rooms, reservations, folios, work orders, key credentials
Operator session lifecycle (open/refresh/close)Authentication, password reset, MFA setup (lives in iam-service)
AI suggestion fetch + decision proxy + auditAI inference, prompt assembly, model selection (lives in ai-orchestrator-service)
Alert inbox + ack proxyAlert authoring, threshold evaluation (lives in notification-service)
Lock-action proxy with audit envelopeLock-vendor protocols, lock state (lives in lock-integration-service)
Device heartbeat + activity ledgerDevice provisioning, MDM (lives in iam-service device registry)
Sync handshake + cursor cacheBulk sync pull/push protocol (lives in sync-service)
Operator preferences cache + mirrorPreferences source-of-truth (tenant-service.operatorPreferences)
Telemetry envelope assemblyDomain events

4. Aggregates owned

Strict definition: aggregates owned by this BFF are session and projection types only. The BFF stores no domain data.

AggregateLifecycleStorage
BackofficeSessionopen on auth → idle → refreshed → closedMemorystore (sessions tier), 12h TTL
DashboardSnapshotper-render projectionMemorystore (cache tier), 30s TTL
WorkbenchViewper-render projection per featureMemorystore (cache tier), 15s TTL
AISuggestionInboxmirror of orchestrator inbox; ack on decisionMemorystore + Postgres ack log
AlertInboxmirror of notification inbox; ack on user actionMemorystore + Postgres ack log
OperatorActivityappend-only log of operator actions for telemetryPostgres ledger, 90d retention
DeviceSyncStatusper-device sync cursor cache + heartbeatPostgres + Memorystore
OperatorPreferencesread-through cache over tenant-service preferencesMemorystore + Postgres mirror, 30d retention
KeyboardShortcutMapserver-rendered map for desktopStatic + Postgres overrides
OfflineActionQueueHintinformational hint about what desktop has queuedPostgres, 7d retention

OfflineActionQueueHint is the most subtle: the desktop owns the canonical outbound queue. The hint here is so we can show "you have N pending actions" on a second device for the same operator without the hint device needing direct connection to the primary device's local SQLite. The hint is best-effort and never authoritative.

5. Responsibilities

#ResponsibilityNotes
1Resolve operator + tenant + property + role from device-bound JWTSingle layer; downstream calls all carry resolved context
2Compose dashboard payloadPer-widget independent fanout with deadlines + skeletons
3Compose workbench view payloadsPer-board: front-desk grid, housekeeping board, maintenance board, today panel, arrivals/departures/in-house
4Proxy AI suggestion fetchesOrchestrator-bounded; per-suggestion category filter
5Proxy AI suggestion decisionsDecision logged here for audit; orchestrator notified async
6Proxy alert acknowledgmentsNotification service is source-of-truth
7Sync handshakeCursor handshake with sync-service; mints sync-session-token; brokers per-device cursor
8Lock-action proxyAdds operator_id + device_id + mfa_attestation envelope to lock-integration-service calls
9Operator preferences read/writeCache + mirror; source-of-truth = tenant-service
10Device heartbeat + activity telemetryHeartbeat every 60 s; activity events on every meaningful click
11SSE multiplexerOne SSE per device; channels: ai, alerts, dashboard-refresh-hints, session
12Force-logout broadcastOn iam.session.revoked.v1 for affected device
13Domain mutation proxy with audit envelopePOST /reservations/{id}/check-in, etc., proxied to domain services with idem keys
14Idempotency safetyAll mutations carry X-Idempotency-Key; we de-dupe in Postgres
15Locale / theme deliveryOperator UI locale + tenant theme bundle URL on bootstrap
16Themed error responsesAll errors carry localized userMessage + MELMASTOON.* code

6. Out of scope (explicit)

  • Domain logic of any kind
  • Direct DB access to domain data
  • Authentication primitives
  • Sync pull/push payload assembly (lives in sync-service)
  • AI inference (lives in ai-orchestrator-service cloud + ONNX Runtime on desktop)
  • Lock vendor SDKs (lives in lock-integration-service)
  • Receipt printing, USB/serial peripheral access (lives in Electron main process)
  • Multi-tenant routing across tenants (one operator session = one tenant)

7. Upstream interactions (composes from)

ServiceCalls
iam-serviceDevice-bound refresh, session validate, force-logout broadcast
tenant-serviceTenant profile, operator role + property scope, preferences mirror
property-serviceProperty metadata, room types, photos for dashboard tiles
reservation-serviceToday's arrivals/departures/in-house, reservation reads, mutation proxies
inventory-serviceOccupancy KPI, dashboard tiles
pricing-serviceRate snapshot tiles for dashboard
housekeeping-serviceBoard view, task lists
maintenance-serviceBoard view, work order list
billing-serviceFolio summary, invoice list
lock-integration-serviceIssue/revoke key proxy with audit envelope
ai-orchestrator-serviceSuggestion fetch, decision report
notification-serviceAlert inbox, ack proxy
sync-serviceHandshake, cursor read/write
analytics-serviceKPI snapshots (occupancy, ADR, RevPAR)

8. Downstream consumers

Single consumer: @ghasi/app-desktop-backoffice (Electron). The desktop renderer talks to the BFF only through window.melmastoon.* IPC bridge, which is implemented in the Electron main process and proxies HTTP calls to this BFF.

9. Key architectural decisions

DecisionRationale
BFF stateless on hot pathSimplifies horizontal scaling; sessions in Memorystore; analytics in Postgres outbox
Per-widget independent SSR with skeletonsOne slow upstream cannot block the dashboard paint
SSE preferred, polling fallbackHostile networks may not allow long-lived connections; desktop falls back automatically
Device-bound JWT with proof-of-possessionReduces token theft blast radius; per-device revocation possible
Sync cursor cached in BFFCheap GET for desktop reconnect; canonical authority is sync-service
Lock-action via BFF, not directAdds audit envelope and ensures MFA attestation is enforced before vendor call
Domain mutations proxied with idem keysDesktop owns idem-key generation; BFF dedupes; safe over flaky links
No telemetry in offline mode (desktop)Desktop emits offline activity to its local outbox; replays via sync; BFF receives those replays via the standard event pipeline

10. Non-functional requirements

ConcernTarget
/dashboard p95 (warm)< 600 ms
/dashboard first-byte (chunked SSR)< 200 ms
Workbench reads p95< 400 ms
Heartbeat p95< 80 ms
Sync handshake p95< 300 ms
Lock-action proxy p95< 800 ms (orchestrator-bounded by lock-integration-service)
AI suggestion fetch p95< 500 ms
SSE first event p95< 200 ms
Availability99.9% (desktop tolerates BFF outage via offline mode)
Concurrent operators per property50 peak
Devices per tenant200 peak
Force-logout fan-out latency< 5 s from event publish to SSE delivery

11. Multi-tenancy

Every request resolves tenantId from the device-bound JWT (subject claim). Cross-tenant access attempts are blocked at:

  • JWT subject claim check
  • Postgres RLS on every owned table
  • Memorystore key namespacing
  • Cache key derivation includes tenantId

Tenant isolation tests: tenant-isolation.spec.ts proves cross-tenant access fails at every layer.

12. Versioning posture

  • API: /bff/backoffice/v1/*. Breaking changes ship at /v2 with overlap window.
  • View-model contracts: provider-side Pact verifies against the desktop's published consumer pacts.
  • Event subjects: per NAMING, vN suffix on every published subject.

13. Repository layout

Per SERVICE_TEMPLATE:

services/bff-backoffice-service/
src/
domain/ # Session/projection aggregates only
application/ # Orchestrators, view-model composers, use cases
infrastructure/ # Upstream gateways, Memorystore, Postgres, Pub/Sub adapters
presentation/ # NestJS controllers, SSE handlers, OpenAPI
main.ts
test/
unit/
integration/
contract/
drizzle/migrations/
openapi/openapi.yaml
*.md (this bundle)

14. Anti-goals

  • Do not implement check-in business rules; orchestrate reservation-service.checkIn.
  • Do not maintain per-room state; read from inventory-service + housekeeping-service.
  • Do not mint AI prompts; orchestrator owns those.
  • Do not sign lock credentials; lock service does that.
  • Do not persist domain data anywhere.