You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

14 KiB

2.4 Leaderboard Service (Port 8083) - Detailed Implementation Plan

Summary

Implement the Leaderboard Service as the read-optimized ranking and statistics service for game outcomes, consistent with implementation decisions used in 2.1 (Question Bank), 2.2 (User), and 2.3 (Game Session).

Runtime stack and conventions:

  • Fiber HTTP service with shared bootstrap and observability.
  • PostgreSQL persistence with EnsureSchema(ctx) startup DDL.
  • Redis for read-optimized ranking cache and top-10 acceleration.
  • Shared backend/shared packages for auth, errors, validation, logging, tracing, metrics, and readiness.

Scope boundary:

  • Modify only backend/services/leaderboard-service/**.
  • Do not modify backend/services/* other than leaderboard-service.
  • Do not modify backend/shared/**.

Decisions Reused from 2.1, 2.2, and 2.3

  1. Service composition pattern:
  • internal/infra/config.FromEnv()
  • logger/metrics/tracer initialization in cmd/main.go
  • repository initialization + EnsureSchema(ctx) at startup
  • /health, /ready, /metrics registration
  • route registration via internal/interfaces/http/routes.go
  1. Persistence and state pattern:
  • PostgreSQL as source of truth.
  • Redis as optional performance layer, non-fatal when unavailable.
  • service remains functional on PostgreSQL when Redis is down.
  1. Error and transport pattern:
  • domain/application errors mapped via shared httputil.SendError.
  • standard response envelope style (success, data) used by existing services.
  1. Inter-service integration approach:
  • HTTP adapters with application interfaces so transport can evolve later without domain changes.
  • explicit DTOs for upstream/downstream contracts.
  1. Test pyramid:
  • unit tests for ranking/statistics logic.
  • HTTP integration tests with in-memory doubles/fakes.
  • optional DB-backed integration tests gated by environment.

Objectives

  1. Provide public leaderboard query endpoints:
  • top 10 scores
  • player ranking and history
  • global statistics
  1. Provide internal score ingestion endpoint for completed sessions.
  2. Ensure deterministic ranking:
  • sort by score descending, then completion duration ascending, then completed_at ascending.
  1. Maintain historical score records for analytics and auditability.
  2. Deliver production-ready observability, readiness checks, and test coverage.

API Endpoints

  • GET /leaderboard/top10
  • GET /leaderboard/players/:id
  • GET /leaderboard/stats
  • POST /leaderboard/update (internal command endpoint)

Auth and Access Rules

  1. Query endpoints:
  • GET /leaderboard/top10, GET /leaderboard/stats are public read endpoints.
  • GET /leaderboard/players/:id requires auth; user can read own detailed history.
  • Admin role may read any player history.
  1. Update endpoint:
  • POST /leaderboard/update is internal-only and requires service/admin auth middleware.
  • Reject anonymous or non-privileged callers.

Inter-Service Contracts

Game Session dependency

Purpose: ingest canonical final session outcomes.

Contract for POST /leaderboard/update request:

  • session_id (string, required)
  • player_id (string, required)
  • player_name (string, required)
  • total_score (int, required, >= 0)
  • questions_asked (int, required, >= 0)
  • questions_correct (int, required, >= 0)
  • hints_used (int, required, >= 0)
  • duration_seconds (int, required, >= 0)
  • completed_at (RFC3339 timestamp, required)
  • completion_type (completed|timed_out|abandoned, required)

Idempotency decision:

  • deduplicate on session_id (unique).
  • repeated update for same session_id returns success with existing persisted record.

User Service dependency

Purpose: optional profile hydration fallback for player display fields.

Decision:

  • leaderboard update request is authoritative for player_name to avoid hard runtime coupling.
  • optional future enrichment can call GET /users/:id, but is not required for step 2.4.

Domain Model

Aggregates:

  • LeaderboardEntry (one per completed session)
  • PlayerRankingSnapshot (derived read model)

Value objects:

  • Rank (positive integer)
  • SuccessRate (0..100 percentage)
  • CompletionType

Domain services:

  • RankingService (ordering and tie-breaks)
  • StatisticsService (global aggregates)

Core invariants:

  1. Each session_id can be ingested once.
  2. Score cannot be negative.
  3. Questions counts cannot be negative.
  4. questions_correct <= questions_asked.
  5. Rank ordering rule is deterministic:
  • score desc
  • duration asc
  • completed_at asc
  1. Top10 response always returns at most 10 entries.

Data Model (PostgreSQL)

leaderboard_entries

  • id UUID PRIMARY KEY
  • session_id VARCHAR(64) NOT NULL UNIQUE
  • player_id VARCHAR(128) NOT NULL
  • player_name VARCHAR(50) NOT NULL
  • score INT NOT NULL
  • questions_asked INT NOT NULL
  • questions_correct INT NOT NULL
  • hints_used INT NOT NULL DEFAULT 0
  • duration_seconds INT NOT NULL
  • success_rate NUMERIC(5,2) NOT NULL
  • completion_type VARCHAR(16) NOT NULL
  • completed_at TIMESTAMPTZ NOT NULL
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Indexes:

  • (score DESC, duration_seconds ASC, completed_at ASC)
  • (player_id, completed_at DESC)
  • (completion_type, completed_at DESC)
  • (created_at DESC)

leaderboard_player_stats

Pre-aggregated player read model for fast rank/profile reads.

  • player_id VARCHAR(128) PRIMARY KEY
  • player_name VARCHAR(50) NOT NULL
  • games_played INT NOT NULL DEFAULT 0
  • games_completed INT NOT NULL DEFAULT 0
  • total_score BIGINT NOT NULL DEFAULT 0
  • best_score INT NOT NULL DEFAULT 0
  • avg_score NUMERIC(10,2) NOT NULL DEFAULT 0
  • avg_success_rate NUMERIC(5,2) NOT NULL DEFAULT 0
  • total_questions BIGINT NOT NULL DEFAULT 0
  • total_correct BIGINT NOT NULL DEFAULT 0
  • best_duration_seconds INT NULL
  • last_played_at TIMESTAMPTZ NULL
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Indexes:

  • (best_score DESC, best_duration_seconds ASC, last_played_at ASC)
  • (last_played_at DESC)

Note:

  • all write updates to leaderboard_player_stats happen transactionally with leaderboard_entries insert in the same repository method.

Redis Usage

Key prefix: lb:

Keys:

  • lb:top10:v1 -> serialized top10 payload (TTL cache)
  • lb:rank:{player_id} -> cached player rank snapshot
  • lb:stats:global:v1 -> cached global stats payload
  • lb:zset:scores -> sorted set score index (optional acceleration, non-authoritative)

Rules:

  1. PostgreSQL remains source of truth.
  2. On successful update ingestion:
  • invalidate lb:top10:v1
  • invalidate lb:stats:global:v1
  • invalidate lb:rank:{player_id}
  • best-effort Redis operations; failures logged and counted, not fatal.
  1. If Redis is unavailable:
  • query endpoints compute from PostgreSQL and still return success.
  • readiness check marks Redis as down but optional.

Endpoint Behavior (Decision Complete)

POST /leaderboard/update

Input: final session outcome payload (see contract above).

Flow:

  1. authenticate internal caller.
  2. validate input fields and invariants.
  3. start transaction.
  4. check existing by session_id.
  5. if existing:
  • return existing normalized entry response (200, idempotent success).
  1. insert into leaderboard_entries.
  2. upsert/refresh leaderboard_player_stats aggregate.
  3. commit transaction.
  4. invalidate related Redis caches.
  5. emit structured log + metrics counter.

Output:

  • persisted leaderboard entry summary including computed success_rate.

GET /leaderboard/top10

Query params:

  • optional completion_type filter (completed|timed_out|abandoned)
  • optional window (24h|7d|30d|all, default all)

Flow:

  1. attempt Redis cache hit for matching key variant.
  2. on miss, query PostgreSQL ordered by ranking rule.
  3. compute rank values (1..N), cap at 10.
  4. cache result with short TTL.

Output:

  • ordered top list with rank, player_id, player_name, score, questions_asked, success_rate, duration_seconds, completed_at.

GET /leaderboard/players/:id

Auth:

  • self or admin.

Query params:

  • page (default 1)
  • page_size (default 20, max 100)

Flow:

  1. auth and ownership/admin check.
  2. fetch player aggregate from leaderboard_player_stats.
  3. fetch paginated history from leaderboard_entries.
  4. compute current global rank from ordering criteria against all players using best_score then tie-breakers.

Output:

  • player summary:
    • current_rank, games_played, best_score, avg_score, avg_success_rate, total_score
  • paginated history list.

GET /leaderboard/stats

Flow:

  1. attempt Redis cache hit.
  2. on miss, aggregate from PostgreSQL.

Returned stats:

  • total_games
  • total_players
  • avg_score
  • avg_success_rate
  • max_score
  • score_p50, score_p90, score_p99
  • updated_at

Package Layout

  • backend/services/leaderboard-service/cmd/main.go
  • backend/services/leaderboard-service/internal/infra/config/config.go
  • backend/services/leaderboard-service/internal/domain/leaderboard/
  • backend/services/leaderboard-service/internal/application/leaderboard/
  • backend/services/leaderboard-service/internal/infra/persistence/ent/
  • backend/services/leaderboard-service/internal/infra/state/
  • backend/services/leaderboard-service/internal/interfaces/http/
  • backend/services/leaderboard-service/tests/

Configuration

Service-specific:

  • LEADERBOARD_PORT (default 8083)
  • LEADERBOARD_TOP_LIMIT (default 10)
  • LEADERBOARD_PLAYER_HISTORY_DEFAULT_LIMIT (default 20)
  • LEADERBOARD_PLAYER_HISTORY_MAX_LIMIT (default 100)
  • LEADERBOARD_CACHE_TTL (default 60s)
  • LEADERBOARD_UPDATE_REQUIRE_AUTH (default true)

Optional integration:

  • GAME_SESSION_BASE_URL (default http://localhost:8080) for future backfill tooling
  • UPSTREAM_HTTP_TIMEOUT (default 3s)

Shared:

  • POSTGRES_*, REDIS_*, TRACING_*, METRICS_*, LOG_LEVEL, ZITADEL_*

Implementation Work Breakdown

Workstream A - Bootstrap, config, wiring

  1. Add internal/infra/config/config.go env parsing.
  2. Wire logger/metrics/tracer in cmd/main.go.
  3. Initialize postgres + redis clients.
  4. Initialize repository and EnsureSchema(ctx).
  5. Register /health, /ready, /metrics.
  6. Build auth middleware and register routes.

Workstream B - Domain and application

  1. Define domain entities, value objects, and domain errors.
  2. Implement ranking and statistics calculation services.
  3. Implement application use-cases:
  • UpdateScore
  • GetTop10
  • GetPlayerRanking
  • GetGlobalStats

Workstream C - Persistence

  1. Implement repository interfaces and SQL-backed repository.
  2. Add EnsureSchema(ctx) DDL for both tables and indexes.
  3. Implement transactional ingestion:
  • insert entry
  • upsert player stats
  1. Implement top10/history/stats query methods.

Workstream D - HTTP interface

  1. Add request/response DTOs with validation tags.
  2. Implement handlers with shared error mapping.
  3. Apply ownership/admin checks for player history endpoint.
  4. Keep response envelope consistent with existing services.

Workstream E - Cache and read optimization

  1. Add Redis cache adapter for top10/stats/rank snapshots.
  2. Implement cache keys, invalidation, and graceful fallback.
  3. Add counters for cache hit/miss and invalidation failures.

Workstream F - Testing

  1. Unit tests:
  • ranking tie-break correctness
  • success-rate calculation
  • stats aggregate math
  • update idempotency behavior
  1. HTTP integration tests:
  • update + top10 happy path
  • duplicate update idempotency
  • player endpoint auth guards
  • stats endpoint and metrics availability
  1. Optional DB-backed tests (env-gated):
  • schema creation
  • transactional consistency
  • unique session_id constraint

Error Handling Contract

  • 400: invalid input or invariant violation
  • 401: missing/invalid auth for protected endpoints
  • 403: ownership/admin violation
  • 404: player not found (for player ranking endpoint)
  • 409: conflicting update (non-idempotent invalid duplicate payload scenario)
  • 500: unexpected internal failures

Observability

  1. Structured logs:
  • include session_id, player_id, endpoint, and operation outcome.
  • avoid PII-heavy payload logging.
  1. Metrics:
  • leaderboard_updates_total{status}
  • leaderboard_top10_requests_total{cache}
  • leaderboard_player_requests_total{status}
  • leaderboard_stats_requests_total{cache}
  • leaderboard_update_latency_seconds
  1. Tracing:
  • endpoint -> application -> repository/cache spans.
  • include attributes for cache hit/miss and DB query class.

Delivery Sequence (3-4 Days)

  1. Day 1: bootstrap/config + schema + repository scaffolding.
  2. Day 2: POST /leaderboard/update transactional ingestion + cache invalidation.
  3. Day 3: query endpoints (top10, players/:id, stats) + auth checks.
  4. Day 4: tests, observability hardening, bugfix buffer.

Verification Commands

From backend/services/leaderboard-service:

go test ./...
go vet ./...

Optional workspace-level check from backend:

go test ./...

Definition of Done

  1. All four endpoints implemented and route protection rules enforced.
  2. Ranking order and tie-break behavior matches functional requirement.
  3. Update ingestion is idempotent by session_id.
  4. /health, /ready, /metrics functional with meaningful checks.
  5. Redis cache fallback behavior works when Redis is unavailable.
  6. go test ./... and go vet ./... pass for leaderboard-service.
  7. No code changes outside backend/services/leaderboard-service/**.

Assumptions and Defaults

  1. Inter-service transport remains HTTP in phase 2.
  2. Leaderboard consistency can be eventual within seconds.
  3. POST /leaderboard/update is called by internal trusted workflow after session termination.
  4. No changes are made in other services or backend/shared during this step.