Appearance
Design Decisions
A record of the significant technical decisions made while building HVC, and why.
GraphQL as a first-class API surface (hvc-api, hvc-admin-api)
- Context: clients need flexible queries (contacts, events, chat, dashboards) and realtime updates.
- Decision: expose both REST (for the Flutter apps) and HotChocolate GraphQL (for the web apps) from the same services; use source generators so resolvers live next to their features.
- Why: one schema for the web clients, strong typing, projections; REST kept for the simpler/older mobile clients.
SignalR as the realtime backbone
- Context: calls, chat, presence, and device control all need push.
- Decision: a single
/hubs/mobileSignalR hub carries meeting flows, chat, presence, peripherals, and WebRTC signaling; a small dedicatedhvc-wsservice handles the per-event call-roster broadcast. - Why: proven, HTTP-friendly, first-class auth; WebRTC signaling over the same channel avoids a second transport.
Pexip Infinity for conferencing
- Context: video calls between web, mobile, and headsets, plus recording.
- Decision: use Pexip (VMRs) rather than building media infrastructure; record via OvenMediaEngine (see Deprecated for the Wowza history).
- Why: enterprise-grade conferencing with an API the services can drive (hvc-api creates VMRs; hvc-sinkapi ingests telemetry).
Multi-tenancy with Keycloak realms + per-tenant databases
- Context: tenants need isolation (data + auth) under one deployment.
- Decision: each tenant gets its own Keycloak realm, SQL database, and subdomain; the connection string is swapped per request (
MultiTenantConnectionInterceptor). - Why: strong isolation, per-tenant auth policy, simpler compliance.
Storage abstraction (hvc-storage)
- Context: recordings, media, and logos need object storage across environments.
- Decision: an
IHvcFileSystemabstraction over Azure Blob / MinIO / S3 / local, selected by config (HvcStorageFactory), with Hashids-based opaque names. - Why: deployments differ (cloud vs on-prem/standalone); swapping providers is config, not code.
GetX + ObjectBox for the Flutter apps
- Context: two Flutter clients (mobile, headset) with offline needs.
- Decision: GetX for state/routing/DI and ObjectBox for local data (chat cache, upload queue, saved profiles).
- Why: pragmatic, lightweight, test-friendly; offline chat/media queueing works well with ObjectBox.
Separate admin console (hvc-admin-web/api)
- Context: running the platform (tenants, plans, environments) is different from tenant-level administration.
- Decision: a dedicated SvelteKit + Houdini admin console backed by
hvc-admin-api, separate from the embedded tenant admin insidehvc-web. - Why: different audience (Hippo staff vs tenant admins), different data model.
Dedicated workers over in-process background jobs
- Context: email, recording download, conference finalization are long-running and retry-heavy.
- Decision: separate services (
hvc-services,hvc-sinkapi,hvc-filePushApi) instead of background tasks in the API. - Why: independent scaling/failure, per-environment instances, clearer ownership.