Skip to content

Architecture

Backlog Hold'em is a browser application backed by a single Go process. The frontend sends planning-poker commands and receives authoritative room snapshots in real time.

System context

Main decisions

  • Ports and adapters: business rules are kept separate from HTTP, SSE, WebSocket, and database details.
  • Authoritative server state: clients render server snapshots and do not decide whether a command, including ending a session, is valid.
  • Two transport modes: HTTP commands with SSE events is the default; WebSocket supports commands and events on one connection.
  • SQLite first: a file-backed SQLite database is the default. PostgreSQL can be selected through configuration.
  • Aggregate persistence: each room, its lifetime revealed-round history, and its final summary are persisted as one JSON document, making the room the consistency boundary.
  • Process-local fan-out: active subscribers are connected through an in-memory event hub. This intentionally limits the current deployment to one backend replica.

Container view

Containers

In production-shaped deployments, one Go process routes all browser traffic:

PathDestinationPurpose
/ and static assetsGo web adapterPrerendered SvelteKit pages, assets, and dynamic-route fallback
/api/*Go backendHTTP commands and snapshots
/events/*Go backendSSE event streams
/ws/*Go backendBidirectional WebSocket sessions

The final image copies the static SvelteKit build to /app/public and sets STATIC_DIR to that location. During hot-reload development, Vite serves the frontend on port 5173 and proxies backend paths to Go on port 8080. SvelteKit prerenders public pages while room routes remain client-rendered; Go serves the generated 200.html fallback for direct room links.

The project documentation is a separately deployable VitePress site. Its static container is not part of the Backlog Hold'em application runtime or its trust boundary.

Backend dependency direction

Backend components

Dependencies point inward:

text
transport/database adapters → application and ports → domain

The core does not depend on transport or persistence implementations. cmd/server is the composition root: it chooses adapters from environment variables, optionally wraps the API with the static-web adapter, and wires the object graph.

Command lifecycle

  1. An HTTP handler or WebSocket adapter decodes a transport message.
  2. The adapter calls the shared application service.
  3. The application loads a room through RoomRepository.
  4. A domain method validates and applies the operation.
  5. The application increments the room version and saves the aggregate.
  6. PublicCopy strips unrevealed vote values and current-round revision counts.
  7. The public snapshot is published through RoomEvents.
  8. SSE and WebSocket subscribers deliver the snapshot to browsers.

Mutations are currently serialized by a mutex in the application service. This is simple and safe for one process, but a multi-instance design would require database-level concurrency control and a distributed event bus. Ending a session is a facilitator-only terminal mutation: the final summary is persisted and subsequent joins and room mutations are rejected.

Backlog Hold'em project documentation