Skip to content

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/mobile SignalR hub carries meeting flows, chat, presence, peripherals, and WebRTC signaling; a small dedicated hvc-ws service 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 IHvcFileSystem abstraction 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 inside hvc-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.

Released under the MIT License.