Skip to content

Feature: Add Game Session Persistence & Redis Caching Layer #9

@webreidi

Description

@webreidi

Goal

Introduce persistence and caching abstractions to support resumable sessions, leaderboards/statistics (future), and performance improvements via Redis.

Motivation

  • Current implementation appears in-memory only; sessions disappear on restart.
  • Redis + SQL Server already provisioned via Aspire but underutilized.
  • Caching word lists and precomputed metadata reduces per-request computation and allocations.

Scope

Implement a persistence layer for active game sessions (Redis) and historical archival (SQL) with clear repository abstractions.

Design Overview

Interfaces:

  • IGameSessionStore
    • Task<GameSession?> GetAsync(gameId)
    • Task SaveAsync(GameSession session)
    • Task ExistsAsync(gameId)
    • Optional: Task DeleteAsync(gameId) when complete
  • ICompletedGameArchive
    • Task ArchiveAsync(GameSessionSummary summary)
  • IWordListCache
    • Task<IReadOnlyList> GetAllAsync()

Data Flow:

  1. User starts game -> new GameSession created (Issue Refactor: Introduce Domain Services & Clean API Layer for Codele #8) -> stored in Redis via IGameSessionStore
  2. Each guess updates session then persists mutated state
  3. On completion, archive lightweight summary to SQL (GameId, Attempts, DurationSeconds, WinFlag, Timestamp)
  4. Optionally remove session from Redis after archival

Caching:

  • Word list loaded once from embedded resource or file into Redis key codele:words:v1
  • Secondary cached structure: letter frequency map (Dictionary<char,int>) for analytics (stretch)

Persistence Technologies:

  • Redis: StackExchange.Redis (already implicitly available in Aspire environment)
  • SQL: Minimal table CompletedGames (Id GUID PK, Attempts INT, Win BIT, DurationSeconds INT, CreatedUtc DATETIME2)

Implementation Plan

  1. Create data contracts (GameSession persistence model, GameSessionSummary)
  2. Implement RedisGameSessionStore (serialize with System.Text.Json source-gen for perf)
  3. Add SQL EF Core DbContext or Dapper micro layer for CompletedGames (keep minimal)
  4. Create migration / ensure table exists on startup (idempotent script)
  5. Implement WordListCache with warm-up on first request; store TTL (e.g., 24h) to allow refresh
  6. Integrate into GameService (from Issue Refactor: Introduce Domain Services & Clean API Layer for Codele #8) behind interfaces
  7. Add configuration toggles: Persistence:ArchiveCompletedGames (bool)
  8. Add metrics counters (games_started, games_completed, games_won) if OpenTelemetry metrics already wired (future if not in this issue)
  9. Tests: unit test serialization round-trip, store CRUD, archive path

Acceptance Criteria

  • Starting a game produces a Redis key (inspect manually) containing session JSON
  • Completing a game writes a row to SQL when archival enabled
  • Word list served from cache after first load (log confirming cache hit)
  • All new tests pass locally

Non-Goals

  • Leaderboards API (future)
  • Metrics exposition (future or separate issue)

Risk & Mitigation

  • Redis serialization bloat -> use explicit DTO + source-gen context
  • SQL migrations complexity -> keep single table, manual command or simple EF Core migration

Observability

  • Log debug entries for cache warm, session save, archive
  • (Optional) Add tracing span attributes: gameId, attemptCount

Definition of Done

PR merged with working Redis session persistence, SQL archival path, documentation in README, and tests.

Estimated Effort

Medium (5-7h).

Depends on: Issue #8 (domain service abstractions).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions