Skip to content

Architecture ​

Backlog Hold'em is a browser application backed by a Go service. The frontend sends planning-poker commands and receives authoritative room snapshots in real time. SQLite deployments run one backend process; PostgreSQL can coordinate multiple replicas.

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.
  • Event-sourced aggregate: immutable room events are the source of truth; bounded current-state and normalized round projections are updated transactionally.
  • Driver-aware fan-out: SQLite uses a process-local hub, while PostgreSQL uses transactional database notifications to reach subscribers on every 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 the current room projection and expected sequence through RoomRepository.
  4. A domain method validates the command, emits a versioned event, and applies it through the aggregate reducer.
  5. The repository appends the event and updates current/round projections atomically; stale versions are reloaded and revalidated.
  6. Room.Snapshot creates a credential-free representation and strips unrevealed vote values and current-round revision counts.
  7. The public snapshot is published through RoomUpdates.
  8. SSE and WebSocket subscribers deliver the snapshot to browsers.

PostgreSQL commits publish a database notification that each replica turns into an authoritative snapshot. SQLite intentionally remains single-process. Ending a session is a facilitator-only terminal event: the final summary is projected and subsequent joins and room mutations are rejected.

Backlog Hold'em project documentation