# backend Internal-only domain service for the Scrabble platform (module `scrabble/backend`). It owns identity/sessions, accounts, and — in later stages — the lobby, game runtime, robot, chat, history and administration. Its only network consumers are the `gateway` and the platform side-services; it is never exposed publicly. As of Stage 1 the backend provides the foundation: configuration, the HTTP listener with the `/api/v1` route-group skeleton and probes, the Postgres pool with embedded goose migrations, OpenTelemetry wiring, an in-memory session cache, and the durable accounts / identities / sessions data model. The session and account REST endpoints are added with the `gateway` (Stage 6); Stage 1 ships the store/service layer they will call. ## Package layout ``` cmd/backend/ # process entrypoint: telemetry -> db+migrate -> cache -> server cmd/jetgen/ # dev tool: regenerate go-jet code from a throwaway container internal/config/ # env configuration (composes postgres + telemetry config) internal/telemetry/ # OpenTelemetry providers + per-request timing middleware internal/postgres/ # pgx-over-database/sql pool (otelsql), goose migrations migrations/ # embedded *.sql (goose), schema `backend` jet/ # generated go-jet models + table builders (committed) internal/account/ # durable accounts + platform/email identities (store) internal/session/ # opaque tokens, sessions store, write-through cache, service internal/server/ # gin engine, route groups, X-User-ID middleware, probes ``` ## Configuration (environment) | Variable | Default | Notes | | --- | --- | --- | | `BACKEND_HTTP_ADDR` | `:8080` | HTTP listen address. | | `BACKEND_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`. | | `BACKEND_POSTGRES_DSN` | — | **Required.** pgx/libpq URL; must pin `search_path=backend`. | | `BACKEND_POSTGRES_MAX_OPEN_CONNS` | `25` | Pool max open connections. | | `BACKEND_POSTGRES_MAX_IDLE_CONNS` | `5` | Pool max idle connections. | | `BACKEND_POSTGRES_CONN_MAX_LIFETIME` | `30m` | Max connection lifetime. | | `BACKEND_POSTGRES_OPERATION_TIMEOUT` | `5s` | Connect attempt + `/readyz` ping bound. | | `BACKEND_SERVICE_NAME` | `scrabble-backend` | OpenTelemetry `service.name`. | | `BACKEND_OTEL_TRACES_EXPORTER` | `none` | `none` or `stdout` (OTLP arrives later). | | `BACKEND_OTEL_METRICS_EXPORTER` | `none` | `none` or `stdout`. | ## Run ```sh docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \ go run ./cmd/backend ``` On boot the backend opens the pool, creates the `backend` schema if needed, and applies the embedded migrations. `GET /healthz` reports liveness; `GET /readyz` reports 200 only when the database answers and the session cache is warmed. ## Migrations & generated code Migrations are plain goose SQL under `internal/postgres/migrations` (sequential `NNNNN_name.sql`), embedded and applied at startup. After changing the schema, regenerate the committed go-jet code (needs Docker): ```sh go run ./cmd/jetgen # rewrites internal/postgres/jet against a temp container ``` ## Tests ```sh go test -count=1 ./... # unit tests (no Docker) go test -tags=integration -count=1 -p=1 ./... # Postgres-backed (needs Docker) ``` Integration tests are guarded by the `integration` build tag and run against a throwaway `postgres:17-alpine` container; they fail loudly when Docker is absent rather than skipping.