About arc42

arc42, the template for documentation of software and system architecture.

Template Version 8.2 EN. (based upon AsciiDoc version), January 2023

Created, maintained and © by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. See https://arc42.org.


Note

This version of the template contains some help and explanations. It is used for familiarization with arc42 and the understanding of the concepts. For documentation of your own system you use better the plain version.

1. Introduction and Goals

YOVI is a web platform for playing the game Y, developed by Micrati.

The system is designed for both human users and automated agents (bots). Its primary goals are to provide a smooth and engaging user experience when playing against an Artificial Intelligence (AI) with configurable board sizes and selectable difficulty levels, and to support real-time online multiplayer matches between human players through a matchmaking system.

YOVI consists of a React-based web frontend (running in the browser), an Auth Service (managing authentication and credentials), a Users Service (managing user profile data), a Game Service (handling match persistence, statistics, online matchmaking, and real-time sessions via Socket.IO), and a Rust-based Game Server (handling game logic and bot strategies). All backend services communicate via REST APIs using JSON messages, with YEN notation for game states, and are accessible through a single Nginx API Gateway.

This section summarizes the most relevant functional requirements from an architectural perspective.

1.1. Requirements Overview

The following table summarizes the main use cases of the system:

Primary Actors Use Case / Functionality Description

Player

Play Game vs AI

The player accesses the web application in their browser and starts a new game of Y against an AI bot. The React frontend displays a board with configurable size. The player takes turns placing pieces on the board. After each player move, the frontend sends the current game state in YEN notation to the Game Server via Nginx. The AI responds with its move, which is applied to the board and shown to the player. The Game Service persists each move in game.db. The system manages turns and checks after each move if the game has ended. The process repeats until a victory or defeat condition occurs.

Player

Online Multiplayer Match

A registered player joins the online matchmaking queue through the React frontend. The Game Service searches for another available player. When two players are matched, a real-time session is created and both players are connected via Socket.IO. Each player takes turns placing pieces; the board state is broadcast in real time to both clients after every move. If no opponent is found within the configured timeout (MM_TIMEOUT_SEC), the system assigns a bot as the opponent (bot fallback).

Player

Turn Timeout and Bot Fallback

During an online match, each player has a limited time to make their move (TURN_TIMEOUT_SEC). If a player does not move within the allowed time, the TurnTimerService triggers the BotFallbackService, which computes and applies a move automatically on the player’s behalf, keeping the game progressing without interruption.

Player

Strategy Configuration

Before or during a game, the player selects a game strategy for the AI and an associated difficulty level through the React UI. The system stores this configuration in the Game Service and uses it whenever it requests the next move from the Game Server. The game state and selected strategy are sent to the Rust Game Server, which calculates the move accordingly.

Registered Player

History and Statistics Management

A registered player accesses their profile within the web application. The React frontend requests statistics from the Game Service via Nginx. The system retrieves information associated with the user from game.db, including total games played, wins, losses, and other performance metrics. This information is presented in a structured manner in the UI, allowing the player to track progress and results over time.

Player

User Registration and Authentication

A new player registers through the React frontend. The Auth Service receives the registration request, hashes the password, and stores the credentials in auth.db. On subsequent visits, the player logs in through the Auth Service, which validates credentials and returns a JWT token used to authenticate requests to other services.

External Bot

API for External Bots

An external bot makes requests to the system API through Nginx to create games, check board state, or make moves. The bot sends the game state using YEN notation to the Game Server and receives responses in the same format. Authentication is handled by the Auth Service against auth.db, and match data is persisted by the Game Service in game.db.

System (Game Server)

Move Validation and Suggestion

The Game Server receives the current game state in YEN notation from the React frontend (via Nginx). The Rust module determines if the game has ended and, if not, calculates the optimal next move according to the selected strategy. The result is returned to the frontend to update the game board. The Game Server is completely stateless and has no database access.

1.2. Quality Goals

The following table outlines the key quality attributes that are most important for the architecture of the YOVI system

Quality Attribute Description

Performance

The system must calculate moves and check victory conditions in suitable timeframes to ensure a smooth gameplay experience, even on variable-size boards.

Scalability

The system must support multiple concurrent games and users, allowing growth without major architectural redesigns.

Maintainability

The architecture should facilitate adding new strategies, Y game variants, or logic changes without impacting the rest of the system.

Interoperability

The system must allow integration with external clients through a well-defined, documented, and interface-independent API.

Usability

The web application should offer a clear and intuitive interface, enabling users to play games and consult information effortlessly.

Security

The system should protect backend services from direct external access, handle authentication properly, and ensure data integrity through controlled and isolated database access per service.

Real-time Responsiveness

Online multiplayer matches must propagate board state updates to all connected players with minimal latency, ensuring a seamless turn-based experience over WebSocket connections.

1.3. Stakeholders

The table below identifies the main stakeholders of the YOVI system, along with their roles, contacts, and expectations regarding the architecture and the delivered solution.

Role/Name Contact Expectations

Micrati (Client)

Micrati Company

Delivery of a functional system that meets all requirements.

Development Team

UO302313@uniovi.es – David Fernando Bolaños Lopez UO294946@uniovi.es – Raúl Velasco Vizán UO301919@uniovi.es – Ángela Nistal Guerrero UO300731@uniovi.es – Olai Navarro Baizán UO301831@uniovi.es – Alejandro Requena Roncero

Develop a robust solution, well-documented and easy to integrate.

Players

Application Users

An attractive web interface, ability to check personal statistics, and AI/bots that provide a real challenge with fast response times and selectable difficulty levels.

Professors

Jose Emilio Labra Gayo

Technical quality in code, a functional project, and coherent architectural documentation.

2. Architecture Constraints

Before starting the design and implementation of YOVI, it is important to be aware of the constraints that will guide architectural decisions. These constraints reflect organizational limitations, technical choices, and mandatory practices that the development team must follow. Understanding them helps ensure that the architecture is feasible, maintainable, and aligned with project requirements.

2.1. Organizational Constraints

The following organizational constraints define the environment in which the YOVI system is developed.

Constraint Explanation

Small team of 5 people

The entire solution must be developed and maintained by a small team, so simplicity, modularity, and ease of integration are prioritized.

Delivery deadlines

The system must meet the deadlines set by the ASW project, including partial and final submissions.

Mandatory testing

Unit, integration, and end-to-end tests must be performed during development to ensure system quality.

2.2. Technical Constraints

Technical constraints specify mandatory technologies, languages, and communication protocols that the system must adopt.

Constraint Explanation

Frontend language: TypeScript

The web application must be implemented in TypeScript, so client-side logic must adhere to this ecosystem.

Frontend framework: React

The web interface must be built using React, which influences component structure and state management.

Game logic module language: Rust

Victory checking and move suggestion are implemented in Rust, constraining the communication interface and available libraries.

Communication via JSON / YEN notation

All exchanges between the web application and the game logic module, as well as with the bot API, must use JSON messages in YEN format.

Mandatory web deployment

The application must be deployed and publicly accessible, influencing architecture design for web hosting and basic scalability.

Data persistence

Storage of users, games, and statistics is required, affecting database selection and backend organization.

3. Context and Scope

Yovi is a distributed web application that allows users and bots to play the Y board game. The system provides a platform where human players can compete against each other or against AI bots, with match history and statistics tracking for registered users.

3.1. Business Context

3.1.1. Communication Partners

business context
Partner Input Output

Human Player (Web User)

  • User registration/login credentials

  • Game moves (position=YEN, strategy, difficulty)

  • Match creation requests (size, strategy, difficulty)

  • Statistics/history queries

  • Authentication tokens/sessions

  • Current game state (YEN format)

  • Valid/invalid move responses

  • Match results and statistics

  • Game history data

Bot Client (External/Internal)

  • Bot authentication

  • Game moves via Bot API (position=YEN, strategy, difficulty)

  • Match participation requests

  • Authentication tokens

  • Current game state (YEN format)

  • Move validation responses

  • Match outcomes

Administrator

  • System monitoring requests

  • User management operations

  • Bot registration/configuration

  • System status

  • User data

  • Bot performance metrics

3.1.2. Domain Concepts

  • YEN (Y-encoded Notation): Domain-specific format for representing board positions and moves in the Y game

  • Match: A game session between two players (human or bot)

  • Strategy: Game difficulty level and bot behavior configuration

  • Anonymous Play: Unregistered users can play but their scores are not persisted

  • Online Session: A real-time multiplayer match between two human players managed via Socket.IO

  • Matchmaking Queue: Redis-backed queue that pairs available players before creating an online session

3.2. Technical Context

3.2.1. System Architecture

technical context deployment

3.2.2. Technical Interfaces

Component Connection Protocol/Technology Port/Channel Purpose

Web Browser → Nginx (API Gateway)

HTTP, REST API

80 (HTTP)

  • Single public entry point

  • Request routing to backend services

  • CORS handling

  • Rate limiting

  • Static file serving (React frontend)

Nginx → Auth Service

HTTP, REST API

3001 (HTTP)

  • User authentication and registration

  • JWT token generation and validation

  • Credential management

  • Password hashing

Nginx → Users Service

HTTP, REST API

3000 (HTTP)

  • User profile retrieval and updates

  • User search operations

Nginx → Game Service

HTTP, REST API + WebSocket (Socket.IO)

3002 (HTTP / WS)

  • Match creation and persistence

  • Move history storage

  • Statistics retrieval

  • Online matchmaking queue management

  • Real-time online session updates via Socket.IO

Nginx → Game Server

HTTP, REST API

4000 (HTTP)

  • Bot move computation

  • Move validation

  • Win condition checking

  • YEN format game logic

Game Service → Redis

Redis protocol

6379 (internal)

  • Matchmaking queue persistence

  • Online session state storage

  • Socket.IO adapter for multi-instance pub/sub

Auth Service → auth.db

SQLite native (embedded)

Local file access

  • Credential CRUD operations

  • JWT token metadata persistence

  • Transaction management

Users Service → users.db

SQLite native (embedded)

Local file access

  • User profile CRUD operations

  • Transaction management

Game Service → game.db

SQLite native (embedded)

Local file access

  • Match state persistence

  • Move history storage

  • Statistics aggregation

React Frontend (in Browser) → Nginx

HTTP, REST API, AJAX, WebSocket

80 (HTTP / WS)

  • API calls to all backend services

  • Game state updates

  • User authentication

  • Statistics queries

  • Real-time Socket.IO connection for online matches

3.2.3. Key API Endpoints

Authentication (via Nginx → Auth Service):

  • POST /api/auth/register - User registration

  • POST /api/auth/login - User authentication, returns JWT token

  • POST /api/auth/refresh - JWT token refresh

  • POST /api/auth/verify - JWT verification endpoint for internal service-to-service authorization checks (must not be exposed publicly)

User Profile Management (via Nginx → Users Service):

  • GET /api/users/{id} - Retrieve user profile

  • PUT /api/users/{id} - Update user profile

  • GET /api/users - Search users

Game Data Management (via Nginx → Game Service):

  • POST /api/game/matches - Create new match (size, strategy, difficulty)

  • GET /api/game/matches/{id} - Retrieve match state

  • POST /api/game/matches/{id}/moves - Persist a move and updated YEN state

  • GET /api/game/stats/{userId} - Retrieve player statistics and match history

Online Matchmaking and Sessions (via Nginx → Game Service):

  • POST /api/game/online/queue - Join the matchmaking queue

  • GET /api/game/online/queue/match - Poll to check if the player has been matched

  • DELETE /api/game/online/queue - Cancel matchmaking

  • GET /api/game/online/sessions/active - Get the current active online session for the authenticated user

  • GET /api/game/online/sessions/:matchId - Get the state of a specific online session

  • POST /api/game/online/sessions/:matchId/moves - Submit a move in an online session

Game Logic (via Nginx → Game Server):

  • POST /api/gamey/play - Submit player move (position=YEN, strategy, difficulty)

    • Returns: Updated YEN board state, move validation status

  • POST /api/gamey/ybot/choose/{botId} - Bot move computation

    • Returns: YenMoveDto + game status

  • GET /api/gamey/validate - Validate move without executing

Bot API (External bots via Nginx):

  • POST /api/gamey/bot/play - Bot move submission (position=YEN, strategy, difficulty)

3.2.4. Data Formats

YEN Notation Example:

{
  "size": 4,
  "turn": "R",
  "players": ["B", "R"],
  "layout": "B/.B/RB./B..R"
}

Move Request/Response:

{
  "position": "B/.B/RB./B..R",
  "strategy": "heuristic",
  "difficulty": 3,
  "nextMove": {
    "player": "R",
    "coordinate": {"x": 0, "y": 1, "z": 2}
  }
}

3.2.5. Deployment Considerations

  • Containerization: All services (React Frontend, Nginx, Auth Service, Users Service, Game Service, Game Server, Redis) are containerized using Docker for consistent deployment

  • Databases: Three independent SQLite files (auth.db, users.db, game.db), each stored as a separate volume mount in its respective service container

  • Real-time State: Online session state and the matchmaking queue are stored in Redis, which runs as an independent container and is accessible only to the Game Service

  • Fault Isolation: A failure or overload in one database does not affect the others; authentication, profile, and game data are independently available

  • Scalability: Game Server is stateless and can be horizontally scaled behind Nginx load balancer; each Node.js service can also be scaled independently

  • Security: Nginx handles CORS, rate limiting, and request validation; no backend service is directly accessible from outside

  • API Gateway: Nginx isolates all backend services from direct external access

  • Frontend Deployment: React app is built and served as static files through Nginx

4. Solution Strategy

4.1. Overview

This section outlines the fundamental architectural and technological decisions that shape the Yovi system. These decisions were made based on project requirements, quality goals, and technical constraints imposed by the academic context.

4.2. Technology Decisions

4.2.1. Distributed Architecture with Microservices

Decision Rationale

Nginx as API Gateway

  • Security: Single public entry point isolates backend services from direct access

  • Routing: Centralized request routing based on URL paths (/api/auth/, /api/users/, /api/gamey/, /api/game/)

  • Cross-cutting concerns: Handles CORS, rate limiting, and static file serving in one place

  • Performance: Efficient reverse proxy with minimal overhead

  • Simplicity: Easy configuration and widely adopted in production environments

Separate Game Server (Rust)

  • Performance: Rust provides memory safety without garbage collection, ensuring fast execution for game logic and move validation

  • Concurrency: Rust’s ownership model enables safe concurrent processing of multiple game sessions

  • Domain Separation: Isolates core game logic from presentation and user management concerns

  • Requirement: Mandated by project specification to implement game engine in Rust

Auth Service (Node.js + Express + TypeScript)

  • Single Responsibility: Exclusively manages user credentials, authentication, and JWT token lifecycle

  • Fault Isolation: A failure in the credentials database does not affect user profiles or game data

  • Security: Isolates sensitive credential data (password hashes, tokens) in a dedicated database (auth.db)

  • Independent Scalability: Can be scaled independently if authentication load increases

Users Service (Node.js + Express + TypeScript)

  • Single Responsibility: Manages user profile data exclusively (users.db)

  • Fault Isolation: A failure in the profile database does not affect authentication or ongoing games

  • REST API: Express simplifies RESTful API implementation

  • Type Safety: TypeScript prevents runtime errors and improves maintainability

Game Service (Node.js + Express + TypeScript)

  • Single Responsibility: Manages all game-related persistence: matches, moves, and statistics (game.db)

  • Fault Isolation: A heavy load of concurrent matches collapses only the game database, leaving authentication and user profiles unaffected

  • Domain Cohesion: Groups all game data in one service aligned with the game domain

  • Independent Scalability: Can be scaled independently under high game load

Frontend (React + Vite + TypeScript)

  • Component-based UI: React’s declarative approach simplifies game board rendering and state management

  • Fast Development: Vite provides instant hot module replacement (HMR) for rapid iteration

  • Type Safety: TypeScript prevents runtime errors and improves code maintainability

  • SPA Architecture: Single-page application enables smooth user experience without page reloads

  • Client-side coordination: Frontend manages API call orchestration from the browser

Databases (SQLite x3)

  • Fault Isolation: Three independent databases (auth.db, users.db, game.db) ensure that a failure or overload in one does not affect the others

  • Simplicity: Zero-configuration embedded database, no separate server process required

  • Portability: Single file databases simplify deployment and backup per service

  • Performance: Excellent read performance for each domain’s specific workload

  • Write-Ahead Logging (WAL): Enables concurrent reads during writes, sufficient for academic project scale

4.3. Architectural Patterns

4.3.1. Microservices Architecture

solution architecture

The system follows a microservices architecture where:

  • Nginx acts as the API Gateway, providing a single entry point and routing requests to appropriate services

  • React Frontend runs in the user’s browser and coordinates API calls to backend services

  • Auth Service is exclusively responsible for authentication, registration, and JWT management, backed by auth.db

  • Users Service manages user profile data, backed by users.db

  • Game Service manages all game-related persistence (matches, moves, statistics), backed by game.db

  • Game Server remains stateless and focuses solely on game domain logic

  • YEN notation defines the boundaries between services for game state communication

  • Each service owns its own database, ensuring fault isolation: a failure or overload in one database does not propagate to the others

4.3.2. Layered Architecture within Services

layered architecture

React Frontend Layers:

  • Components: React UI components for board, moves, statistics

  • State Management: Local game state and user session (React hooks/context)

  • API Layer: Abstraction over backend API calls

  • HTTP Client: Fetch/Axios for making HTTP requests to Nginx

Auth Service Layers:

  • AuthController: Handles HTTP requests for registration and login

  • AuthService: Business logic for password hashing and JWT management

  • CredentialsRepository: Abstracts access to auth.db

Users Service Layers:

  • UsersController: Handles HTTP requests for user profile operations

  • UserService: Business logic for user profile management

  • UserRepository: Abstracts access to users.db

Game Service Layers:

  • GameController: Handles HTTP requests for match and stats operations

  • MatchService: Business logic for match creation, move persistence, and lifecycle

  • StatsService: Aggregates and retrieves player statistics

  • MatchRepository / StatsRepository: Abstract access to game.db

Game Server Design:

  • Domain-Driven Design: Game, Board, Player, Move as core entities

  • Strategy Pattern: Multiple bot strategies with configurable difficulty levels

  • Encapsulation: Pure domain logic with no external dependencies

4.4. Quality Goals Achievement

Quality Goal Strategy Implementation

Performance

  • Rust for compute-intensive game logic

  • SQLite for fast read operations per service

  • Stateless game server for horizontal scaling

  • Nginx for efficient request routing

  • Move validation in O(n) time

  • Indexed database queries per domain

  • Concurrent request handling in Rust

  • Nginx caching for static files

Scalability

  • Stateless services enable horizontal scaling

  • Each database optimized for its own workload

  • Nginx can load balance multiple instances per service

  • Multiple game server instances possible

  • Independent service and database scaling

  • Caching strategies for frequent queries

  • Each service scales according to its own load

Maintainability

  • Clear separation of concerns across services and databases

  • Type safety (TypeScript + Rust)

  • Each service owns its data domain

  • Layered architecture in each service

  • Repository pattern for DB abstraction per service

  • API documentation

  • Shared YEN notation contracts

Interoperability

  • REST API with JSON

  • Standardized YEN notation

  • External bot API support

  • HTTP-based communication

  • Versioned API endpoints

  • Cross-team bot competition support

  • Well-defined API contracts

Security

  • Nginx isolates backend services

  • Rate limiting and CORS at gateway level

  • Authentication isolated in Auth Service

  • Credential data separated from profile and game data

  • JWT token-based authentication (Auth Service)

  • Password hashing (bcrypt/Argon2)

  • No direct backend access

  • Input validation at API boundaries

  • auth.db isolated from users.db and game.db

4.5. Key Constraints

Constraint Impact on Architecture

Mandated Technologies

TypeScript for frontend/backend and Rust for game engine are project requirements, limiting technology choices

YEN Notation

All game state communication must use YEN format, requiring parsing/serialization layers

Bot API Requirement

System must expose external API for bot clients, influencing authentication and API design

Academic Timeline

Limited development time favors familiar technologies (React, Express) and simple deployment (SQLite)

Web Deployment

Application must be publicly accessible, requiring containerization and reverse proxy strategy

Multiple Strategies

Game engine must support extensible bot strategies, leading to Strategy pattern adoption

Database Fault Isolation

Three independent databases (auth.db, users.db, game.db) ensure that overload or failure in one domain does not affect the others

4.6. Organizational Decisions

  • Monorepo structure: All services in one repository (webapp/, users/, auth/, gamey/, gameservice/, nginx/) for easier coordination

  • Docker Compose: Simplified local development with single command startup

  • Shared YEN notation: Common understanding of game state format across all services

  • API-first approach: Define API contracts before implementation

  • Version control: Git with feature branch workflow and code reviews

  • Documentation: arc42 for architecture, inline code documentation, README files per service

5. Building Block View

5.1. Whitebox Overall System

5.1.1. Overview Diagram

level1 overview

Motivation:

The Yovi system is decomposed into six main subsystems to achieve separation of concerns, independent scalability, technology-specific optimization, and fault isolation across data domains:

  • Nginx (API Gateway): Single public entry point for all requests, handling routing, rate limiting, and CORS

  • React Frontend (Webapp): User interface layer running in the browser, managing UI state and coordinating API calls to backend services

  • Auth Service: Authentication layer with exclusive access to auth.db, managing credentials, registration, login, and JWT tokens

  • Users Service: Profile layer with exclusive access to users.db, managing user profile data

  • Game Service: Game data and online play layer with exclusive access to game.db and Redis, managing match persistence, move history, statistics, online matchmaking, and real-time sessions via Socket.IO

  • Game Server: Pure game logic engine optimized for performance (Rust), completely stateless

  • Redis: Ephemeral in-memory store exclusively used by the Game Service for matchmaking queue state and active online session snapshots

Each service owns its own data store. A failure or overload in one store does not propagate to the others, achieving fault isolation at the data layer.

Contained Building Blocks:

Building Block Responsibility

Nginx (API Gateway)

  • Single public entry point on port 80

  • Routes requests based on URL paths:

    • /api/auth/* → Auth Service (port 3001)

    • /api/users/* → Users Service (port 3000)

    • /api/game/* → Game Service (port 3002)

    • /api/gamey/* → Game Server (port 4000)

    • /* → React Frontend static files

  • Handles CORS and rate limiting

  • Serves React frontend static files

  • Isolates backend services from direct external access

React Frontend (Webapp)

  • Single-page application (SPA) running in user’s browser

  • Renders game board and UI components using React

  • Handles user interactions (login, game moves, statistics viewing)

  • Manages client-side state (game state, user session)

  • Makes HTTP API calls to all backend services through Nginx

  • Establishes a Socket.IO connection for real-time online match updates

  • Coordinates game flow from the client side

Auth Service

  • Single point of access to auth.db

  • Manages user registration and login

  • Password hashing (bcrypt/Argon2)

  • JWT token generation and validation

  • Token refresh logic

  • Exposes REST API on port 3001 (internal)

Users Service

  • Single point of access to users.db

  • Manages user profile data (username, avatar, preferences)

  • Handles user CRUD operations

  • Provides user search and retrieval

  • Exposes REST API on port 3000 (internal)

Game Service

  • Single point of access to game.db and Redis

  • Manages match lifecycle (creation, updates, completion) for both AI and online matches

  • Persists move history and serialized YEN board states

  • Aggregates and provides player statistics and rankings

  • Implements Redis-backed matchmaking queue to pair online players

  • Manages real-time online sessions via Socket.IO (with @socket.io/redis-adapter)

  • Enforces per-turn timeouts and triggers automatic bot fallback when a player does not respond

  • Supports reconnection grace periods before penalising disconnected players

  • Exposes REST API on port 3002 (internal) and Socket.IO on the same port

Game Server (Rust)

  • Implements core Y game rules and logic

  • Validates moves and checks win conditions

  • Computes bot moves using various strategies (random, heuristic, neural network)

  • Completely stateless service using YEN notation

  • Exposes REST API on port 4000 (internal)

  • No database access, pure computation

Redis

  • In-memory store exclusively used by the Game Service

  • Persists matchmaking queue entries (players waiting for an opponent)

  • Stores active online session snapshots (board state, turn, players)

  • Acts as the pub/sub adapter for Socket.IO when multiple Game Service instances are running

  • Data is ephemeral: session state is rebuilt or discarded when a match ends

  • Exposed internally on port 6379

auth.db (SQLite)

  • Stores user credentials (hashed passwords)

  • Persists JWT token metadata and refresh tokens

  • Accessible only through Auth Service

  • Isolated from profile and game data

users.db (SQLite)

  • Stores user profile information (username, avatar, preferences)

  • Accessible only through Users Service

  • Isolated from credentials and game data

game.db (SQLite)

  • Stores match records and metadata (size, strategy, difficulty, status)

  • Persists move history and serialized YEN board states

  • Maintains player statistics and rankings

  • Accessible only through Game Service

  • Isolated from credentials and profile data

Important Interfaces:

  • User → Nginx: HTTP REST API and WebSocket (port 80, public)

  • Nginx → React Frontend: Static file serving (HTML, JS, CSS)

  • Nginx → Auth Service: REST API (/api/auth/*, port 3001, internal)

  • Nginx → Users Service: REST API (/api/users/*, port 3000, internal)

  • Nginx → Game Service: REST API (/api/game/*, port 3002, internal) + Socket.IO WebSocket

  • Nginx → Game Server: REST API (/api/gamey/*, port 4000, internal)

  • React Frontend → Nginx: AJAX/Fetch API calls and Socket.IO WebSocket connection from browser

  • Game Service → Redis: Redis protocol (port 6379, internal) for matchmaking queue and session state

  • Auth Service → auth.db: SQLite native SQL queries

  • Users Service → users.db: SQLite native SQL queries

  • Game Service → game.db: SQLite native SQL queries

Communication Flow Example (AI Match):

  1. User loads the app → Nginx serves React static files → React runs in browser

  2. User clicks "Login" → React calls POST /api/auth/login → Nginx routes to Auth Service

  3. Auth Service validates credentials against auth.db → Returns JWT token

  4. User creates a match → React calls POST /api/game/matches → Nginx routes to Game Service → persisted in game.db

  5. User makes a move → React calls POST /api/gamey/play → Nginx routes to Game Server → returns updated YEN

  6. Game Server returns move validation → React calls POST /api/game/matches/{id}/moves → Game Service persists in game.db

  7. React updates UI

Communication Flow Example (Online Match):

  1. User joins matchmaking → React calls POST /api/game/online/queue → Game Service stores entry in Redis

  2. React polls GET /api/game/online/queue/match → Game Service checks Redis for a paired opponent

  3. When matched, Game Service creates an online session in Redis and returns match details

  4. Both clients connect via Socket.IO → receive real-time board state updates after each move

  5. Player submits a move → POST /api/game/online/sessions/:matchId/moves → Game Service validates, updates Redis session, broadcasts updated state via Socket.IO

  6. If a player exceeds their turn time → TurnTimerService fires → BotFallbackService computes and applies a move automatically

5.2. Level 2

5.2.1. White Box: Auth Service

level2 auth

Contained Building Blocks:

Component Responsibility

AuthController

  • Exposes REST API endpoints: POST /api/auth/register, POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/verify (internal only)

  • Input validation and error handling

  • Request/response mapping

AuthService

  • User registration logic

  • Password hashing and verification (bcrypt/Argon2)

  • JWT token generation and validation

  • Token refresh logic

CredentialsRepository

  • Database access layer for auth.db

  • SQL query execution and result mapping

  • Stores hashed passwords and token metadata

5.2.2. White Box: Users Service

level2 users

Contained Building Blocks:

Component Responsibility

UsersController

  • Exposes REST API endpoints: POST /createuser

  • Exposes Prometheus metrics at /metrics

  • Exposes Swagger UI at /api-docs

  • Input validation and request/response mapping

UserService

  • CRUD operations for user profiles

  • User search and retrieval

  • Profile validation logic

UserRepository

  • Database access layer for users.db

  • SQL query execution and result mapping

  • Transaction management for profile operations

5.2.3. White Box: Game Service

level2 gameservice

Contained Building Blocks:

Component Responsibility

GameController

  • Exposes all REST API endpoints for AI matches, statistics, matchmaking, and online sessions

  • Input validation and JWT authentication middleware

  • Request/response mapping and error delegation

MatchService

  • Match creation and lifecycle management for AI matches

  • Stores match metadata (size, strategy, difficulty, participants)

  • Updates match status (ongoing/finished)

  • Persists serialized YEN board states and move history

StatsService

  • Aggregates player statistics (wins/losses)

  • Calculates win rate and other performance metrics

  • Provides historical match data per user

MatchmakingService

  • Manages the Redis-backed matchmaking queue

  • Adds, polls, and removes players from the queue

  • Pairs two players when compatible entries are found

  • Triggers bot fallback if no opponent is found within MM_TIMEOUT_SEC

OnlineSessionService

  • Creates and manages real-time online session state in Redis

  • Validates and applies moves during online matches

  • Delegates turn timing to TurnTimerService

  • Delegates automatic bot moves to BotFallbackService

  • Supports reconnection grace periods (RECONNECT_GRACE_SEC)

TurnTimerService

  • Starts a countdown for each player’s turn

  • Fires a timeout event after TURN_TIMEOUT_SEC seconds

  • Notifies OnlineSessionService to trigger bot fallback

BotFallbackService

  • Called when a player’s turn times out or no opponent is found during matchmaking

  • Calls the Game Server (Rust) to compute a valid move

  • Applies the bot move to the active session on behalf of the player

MatchRepository

  • Database access layer for match records in game.db

  • Stores and retrieves match metadata and move history

StatsRepository

  • Database access layer for statistics in game.db

  • Optimised read queries for per-user rankings and metrics

MatchmakingRepository

  • Redis access layer for the matchmaking queue

  • Stores and retrieves queue entries per player

OnlineSessionRepository

  • Redis access layer for active online session snapshots

  • Stores board state, current turn, player list, and session metadata

SocketServer

  • Initialises the Socket.IO server on port 3002

  • Uses @socket.io/redis-adapter for pub/sub across instances

  • Handles client connection, disconnection, and reconnection events

  • Broadcasts updated board state to all players in a session after each move

VerifyJwt (Middleware)

  • Intercepts authenticated requests

  • Calls Auth Service internally (/api/auth/verify) to validate the JWT token

  • Attaches userId and username to the request context on success

ErrorHandler (Middleware)

  • Global Express error handler

  • Maps domain errors (e.g., MatchNotFoundError, UnauthorizedMatchError, InvalidMoveError) to appropriate HTTP status codes

  • Prevents leaking internal error details to clients

5.2.4. White Box: Game Server

level2 gameserver

Motivation:

The Game Server encapsulates all game-specific logic using Domain-Driven Design principles. The Strategy pattern allows for extensible bot implementations without modifying core game logic. This service is completely stateless and has no database dependencies. All persistence is delegated to the Game Service.

Contained Building Blocks:

Component Responsibility

EngineService

  • REST API entry point (POST /api/gamey/play, POST /api/gamey/ybot/choose/{botId})

  • Parses YEN notation from requests

  • Delegates to NextMoveComputer or WinEvaluator

  • Returns YenMoveDto responses

  • Stateless request handling

NextMoveComputer

  • Computes next optimal move for a given bot strategy

  • Selects appropriate Strategy implementation based on botId/difficulty

  • Returns valid move in YEN notation

  • No side effects, pure computation

WinEvaluator

  • Checks if game has ended (win condition met)

  • Analyzes board for Y-connections (3-sided paths)

  • Returns game status (ongoing/won/draw)

  • Deterministic evaluation

Strategy Interface

  • Defines contract for bot implementations

  • compute_move(board: &Board, difficulty: u8) → Move

  • Enables extensibility for new strategies

RandomStrategy

  • Implements random valid move selection

  • Low difficulty, fast execution

  • Used for testing and beginner difficulty

  • No lookahead or evaluation

HeuristicStrategy

  • Implements heuristic-based move evaluation

  • Considers board control, connection potential, blocking opponent

  • Configurable difficulty levels affect search depth

  • Position evaluation function

NeuralNetworkStrategy

  • Implements move selection using a trained neural network model

  • Model weights loaded at startup from a file bundled with the service

  • Provides stronger play than the heuristic strategy at higher difficulty levels

  • Stateless inference, no runtime training

GameDomain Package

  • Game: Aggregates board, players, move history, game state

  • Board: Hexagonal grid representation using barycentric coordinates

  • Move: Represents player action (position + player)

  • Player: Entity representing B or R player

  • Coordinate: Barycentric coordinate (x, y, z)

  • WinChecker: Algorithm to detect Y-shaped winning connections

6. Runtime View

This section describes how the main components of the YOVI system collaborate at runtime to support user authentication, match management, gameplay, bot interaction, statistics retrieval, and real-time online multiplayer sessions. The scenarios below are derived directly from the system’s architecture and message flows, using JSON/YEN as the canonical representation of game states.

All communication between the React Frontend (running in the user’s browser) and backend services goes through the Nginx API Gateway, which routes requests to the appropriate service based on URL paths.

6.1. Runtime Scenario 1: User Registration and Login

This scenario shows how a human user registers or logs into the system through the React Frontend. The frontend makes API calls through Nginx, which routes authentication requests to the Auth Service, which persists credential data in auth.db.

runtime login

Notable Aspects:

  • Authentication is fully isolated in the Auth Service and its dedicated auth.db

  • Nginx routes all /api/auth/* requests to Auth Service (port 3001)

  • React Frontend stores the JWT token (localStorage or sessionStorage)

  • auth.db acts as the single source of truth for credentials

  • A failure in other databases (users.db, game.db) does not affect the login flow

6.2. Runtime Scenario 2: Authorization Check

When a user requests protected resources, the React Frontend includes the JWT token in the request headers. Nginx forwards the request to the appropriate service, which delegates JWT verification to Auth Service through an internal call to /api/auth/verify.

runtime auth check

Notable Aspects:

  • JWT validation is responsibility of the Auth Service

  • Other services (Game Service, Users Service) delegate token verification to Auth Service via internal call

  • React Frontend handles unauthorized responses by redirecting to login

  • Nginx simply routes requests without authentication logic

Verify endpoint contract used by internal services:

  • Method and path: POST /api/auth/verify

  • Internal URL (Docker network): AUTH_INTERNAL_VERIFY_URL=http://auth:3001/api/auth/verify

  • Token input: preferred Authorization: Bearer <accessToken> (compatible fallback: JSON body { "token": "<accessToken>" })

  • Success response (200): { "valid": true, "claims": { "sub": "<userId>", "username": "<username>", "tokenType": "access", "iat": <epoch>, "exp": <epoch> } }

  • Error response (401): { "valid": false } for missing, malformed, invalid, expired, or non-access tokens

  • Security boundary: /api/auth/verify must not be exposed publicly through Nginx and should be consumed only over internal service-to-service network calls

  • Client resilience policy for internal callers: use short timeouts (500ms to 1s) and 0-1 retries to avoid cascading failures

6.3. Runtime Scenario 3: Match Creation and Initial State Retrieval

A user creates a new match by specifying board size, strategy, and difficulty through the React UI. The Game Service persists the match in game.db and returns the initial YEN state.

runtime create match

Notable Aspects:

  • The Game Service owns match lifecycle and persistence in game.db

  • Initial game state is always stored and returned in YEN format

  • React Frontend renders the game board based on received YEN notation

  • Match creation requires authentication (JWT token verified against Auth Service)

  • auth.db and users.db are not involved in this flow

6.4. Runtime Scenario 4: Player Move (Human Turn)

The human player submits a move by clicking on the game board in React. The frontend sends the current game state to the Game Server for bot move computation, then persists the updated state in the Game Service.

runtime human move

Notable Aspects:

  • React Frontend coordinates the multi-step flow from the browser

  • Game Server (Rust) is stateless and only computes moves based on YEN input, with no DB access

  • Game Service is the authoritative source of match state persistence in game.db

  • All updates are persisted atomically in game.db

  • auth.db and users.db are not involved in this flow

6.5. Runtime Scenario 5: Bot API Interaction

External bots can play matches programmatically using the API. The flow mirrors the human move scenario but uses bot credentials for authentication against the Auth Service.

runtime bot api

Notable Aspects:

  • Bot authentication is handled exclusively by Auth Service against auth.db

  • Game data (match state, moves) is managed exclusively by Game Service against game.db

  • External bots must follow the same YEN notation format as human players

  • A failure in users.db does not affect bot gameplay

6.6. Runtime Scenario 6: Statistics and History Retrieval

Users can request aggregated statistics such as number of matches, wins/losses, and performance metrics through the React UI. Statistics are served by the Game Service from game.db.

runtime stats

Notable Aspects:

  • Statistics are computed from persisted match data in game.db

  • The Game Service exposes a unified Stats API

  • React Frontend handles visualization (charts, tables)

  • All stats queries require authentication (JWT verified against Auth Service)

  • auth.db and users.db are not involved in this flow

6.7. Runtime Scenario 7: Online Matchmaking

A registered player joins the matchmaking queue looking for a human opponent. The Game Service stores the entry in Redis and waits for a second player. When two compatible players are found, an online session is created and both clients are notified.

runtime matchmaking

Notable Aspects:

  • The matchmaking queue is stored entirely in Redis, keeping game.db free from ephemeral queue state

  • Both players poll independently; neither blocks the other’s request

  • If no opponent is found within MM_TIMEOUT_SEC, the Game Service assigns a bot as the opponent and creates the session automatically

  • Once a session is created in Redis, both clients proceed to connect via Socket.IO (see Scenario 8)

6.8. Runtime Scenario 8: Real-time Online Session (Human vs Human)

After being matched, both players connect via Socket.IO. The Game Service manages the session in Redis, enforces turn order, and broadcasts board state updates to both clients in real time after each move.

runtime online session

Notable Aspects:

  • All real-time communication uses Socket.IO over WebSocket, proxied through Nginx

  • The authoritative session state is kept in Redis for low-latency reads and updates

  • game.db is written for every move (durable history) while Redis holds the live session snapshot

  • The @socket.io/redis-adapter ensures events are delivered correctly even if multiple Game Service instances are running

  • When the game ends, the session is removed from Redis and the final result is persisted in game.db

6.9. Runtime Scenario 9: Turn Timeout and Bot Fallback

During an online session, each player has a limited time (TURN_TIMEOUT_SEC) to place their piece. If the timer expires, the TurnTimerService fires and BotFallbackService automatically computes and applies a move on behalf of the inactive player.

runtime turn timeout

Notable Aspects:

  • TurnTimerService starts a new countdown after each move (human or bot)

  • BotFallbackService calls the stateless Game Server (Rust) to compute the fallback move, ensuring consistency with the standard bot logic

  • Both clients receive a botFallback: true flag in the board update event so the UI can inform players that the move was automatic

  • The fallback move is persisted in game.db with an auto marker for auditability

  • If a player disconnects, the RECONNECT_GRACE_SEC window is given before the timeout countdown begins

7. Deployment View

The deployment view describes the technical infrastructure we use to run the Yovi system. It also shows how the software parts (building blocks) connect to that infrastructure.

Because our system uses Docker containers, we describe the deployment in two main environments: the Production Environment and the Development Environment.

7.1. Infrastructure Level 1 - Production Environment

This section describes the main production environment where the final users can access the system.

Overview Diagram

deployment level1 architecture

Motivation

The main goal of this deployment strategy is to find a good balance between working fast and keeping the system secure. Software needs hardware to run. By using a Virtual Machine (VM) as the main host, the system creates a secure border at the hardware level. Inside this VM, we use Docker containers to run the apps. This makes sure that the deployment is isolated and prevents problems with installed programs on the host machine.

Quality and/or Performance Features

  • Security (Defense in Depth): The infrastructure has two layers of isolation. If a container is attacked, the VM acts as an external wall that protects the physical server. Also, the Nginx API Gateway is the only part connected to the public internet.

  • Portability: By using Docker containers, the environment is always the same. The application works exactly the same way, no matter what physical server is running the Virtual Machine.

  • Independent Scalability: The system is divided into specific microservices (React Frontend, Auth Service, Users Service, Game Service, Game Server). Because of this, developers can give more resources to a specific service without affecting the others.

  • Fault Isolation: Each service owns its own data store. An overload or failure in game.db does not affect auth.db or users.db. Similarly, a Redis failure only affects online matchmaking and active sessions, while AI matches and authentication remain fully operational.

Mapping of Building Blocks to Infrastructure

Software Artifact (Building Block) Infrastructure Element

React Frontend (Webapp)

Deployed as static files (HTML, CSS, JS bundle) served by Nginx on port 80. The React application runs entirely in the user’s browser. Built with Vite and bundled for production.

Nginx (API Gateway)

Deployed in a Docker container inside the VM. It works as a Reverse Proxy and API Gateway. It is the only public entry point (Port 80) to route all incoming traffic. Handles CORS, rate limiting, WebSocket proxying for Socket.IO, and serves the React frontend static files. Routes API requests to backend services.

Auth Service (Node.js + Express + TypeScript)

Deployed in a Docker container inside the VM (Port 3001, internal only). Exclusively manages user credentials, registration, authentication, and JWT token lifecycle. No other service has access to auth.db.

Users Service (Node.js + Express + TypeScript)

Deployed in a Docker container inside the VM (Port 3000, internal only). Exclusively manages user profile data. No other service has access to users.db. Exposes Prometheus metrics at /metrics.

Game Service (Node.js + Express + TypeScript)

Deployed in a Docker container inside the VM (Port 3002, internal only). Exclusively manages match persistence, move history, player statistics, online matchmaking, and real-time sessions via Socket.IO. No other service has access to game.db or the Redis matchmaking/session data.

Game Server (Rust)

Deployed in a Docker container inside the VM (Port 4000, internal only). Calculates game logic and bot movements in YEN format. Completely stateless service optimized for performance. No database access whatsoever.

Redis

Deployed in a Docker container inside the VM (Port 6379, internal only). Used exclusively by the Game Service to store the matchmaking queue and active online session state. Data is ephemeral: no host volume is required. Acts as the pub/sub adapter for Socket.IO.

Prometheus

Deployed in a Docker container inside the VM (Port 9090). Scrapes metrics from the Users Service at regular intervals. Configuration located in users/monitoring/prometheus/.

Grafana

Deployed in a Docker container inside the VM (Port 9091). Connects to Prometheus as a data source and displays system dashboards. Dashboard provisioning located in users/monitoring/grafana/provisioning/.

auth.db (SQLite)

A database file saved on a dedicated Host Volume inside the VM, connected exclusively to the Auth Service container. Stores user credentials and JWT token metadata. Only Auth Service has read/write permissions.

users.db (SQLite)

A database file saved on a dedicated Host Volume inside the VM, connected exclusively to the Users Service container. Stores user profile data. Only Users Service has read/write permissions.

game.db (SQLite)

A database file saved on a dedicated Host Volume inside the VM, connected exclusively to the Game Service container. Stores matches, move history, and statistics. Only Game Service has read/write permissions.

7.2. Infrastructure Level 1 - Development Environment

Following the arc42 rules for multiple environments, the development environment uses the same Docker containers to be equal to production, but without the secure Virtual Machine.

Motivation

Developers need a fast and reliable environment to write and test code on their computers. We use Docker Compose to start the whole system on the developer’s local machine.

Quality and/or Performance Features

  • Consistency: It solves the classic "it works on my machine" problem.

  • Fast Setup: A new developer can start the complete system (React Frontend, Nginx, Auth Service, Users Service, Game Service, Game Server, Redis, Prometheus, and Grafana) with just one docker-compose up command.

Mapping of Building Blocks to Infrastructure

In this environment, the mapping is exactly the same as in Production. The only difference is the host hardware: * Infrastructure Element: The developer’s personal computer (Localhost) running Docker Desktop, instead of a Cloud Virtual Machine.

7.3. Infrastructure Level 2

Here we explain the internal structure of the Docker environment from Level 1. We zoom into the isolated network and how data is saved.

deployment level2 dataflow

Internal Structure Explanation:

This level shows the internal Docker network rules. The Nginx API Gateway completely isolates the microservices from the public internet. External users can never reach the backend services directly. All requests must go through Nginx on port 80.

The request flow is as follows:

  • User browser loads the app: Nginx serves React static files (HTML, CSS, JS)

  • React app runs in browser: Makes API calls to /api/auth/, /api/users/, /api/game/ and /api/gamey/

  • React app connects via Socket.IO for real-time online sessions through /api/game/socket.io/

  • Nginx routes API requests based on URL path:

    • /api/auth/* → Auth Service (port 3001), except /api/auth/verify (blocked externally with 403)

    • /api/users/* → Users Service (port 3000)

    • /api/game/* → Game Service (port 3002), including WebSocket upgrade for Socket.IO

    • /api/gamey/* → Game Server (port 4000)

    • /* (all other paths) → React frontend static files

Example User Flow (AI Match):

  1. User opens http://yourapp.com in browser

  2. Nginx serves index.html and React bundle from static files

  3. React app loads in browser, displays login screen

  4. User clicks "Login" → React sends POST /api/auth/login to Nginx

  5. Nginx routes request to Auth Service (port 3001)

  6. Auth Service validates credentials against auth.db and returns JWT token

  7. React stores token and creates a new match → React sends POST /api/game/matches to Nginx

  8. Nginx routes request to Game Service (port 3002)

  9. Game Service persists the new match in game.db and returns the match ID

  10. User makes a move → React sends POST /api/gamey/play to Nginx

  11. Nginx routes game logic request to Game Server (port 4000)

  12. Game Server computes the bot move and returns the updated YEN state

  13. React persists the move → sends POST /api/game/matches/{id}/moves to Nginx

  14. Nginx routes to Game Service (port 3002), which stores the move in game.db

  15. React updates the UI with the new board state

Example User Flow (Online Match):

  1. User joins matchmaking → React sends POST /api/game/online/queue to Nginx

  2. Game Service stores the queue entry in Redis and waits for an opponent

  3. React polls GET /api/game/online/queue/match until a match is found

  4. Once matched, React establishes a Socket.IO WebSocket connection via Nginx to Game Service (port 3002)

  5. Both players receive real-time board updates via Socket.IO after each move

  6. If a player exceeds their turn time, TurnTimerService triggers BotFallbackService, which computes and applies an automatic move

  7. When the game ends, the session is removed from Redis and the result is persisted in game.db

Internal Auth verification call (service-to-service only):

  • Internal URL env var: AUTH_INTERNAL_VERIFY_URL=http://auth:3001/api/auth/verify

  • Recommended timeout in callers: 500ms to 1s

  • Recommended retries: 0-1 (avoid retry storms)

Data Storage and Fault Isolation:

Data storage is strictly controlled through independent volumes and in-memory stores, all inside the VM:

  • auth.db is mounted exclusively inside the Auth Service container. Only Auth Service has read/write permissions. Stores credentials and JWT metadata.

  • users.db is mounted exclusively inside the Users Service container. Only Users Service has read/write permissions. Stores user profile data.

  • game.db is mounted exclusively inside the Game Service container. Only Game Service has read/write permissions. Stores matches, moves, and statistics.

  • Redis runs as an ephemeral container (no host volume). Only the Game Service connects to it. Stores the matchmaking queue and active online session snapshots. A Redis failure only affects online matchmaking and active sessions; AI matches and authentication remain fully operational.

The Game Server has no database access and operates in a completely stateless manner. All game state persistence is delegated to the Game Service after each move.

Network Isolation:

All backend services (Auth Service, Users Service, Game Service, Game Server, Redis) run on an internal Docker network. They cannot be accessed directly from outside the VM. Only Nginx (port 80), Prometheus (port 9090), and Grafana (port 9091) have public-facing ports exposed. This architecture ensures that:

  • Backend services are protected from direct attacks

  • All requests go through Nginx security layers (rate limiting, CORS)

  • Each database is isolated behind its own service container

  • Services communicate only through defined internal APIs

  • Redis is only reachable from within the Docker network

8. Cross-cutting Concepts

8.1. Domain Model

A first version of the domain model of the application, representing all the entities that interact with the application and their relations, is shown in the following diagram:

domain model

8.2. User Experience (UX)

Since the project includes a web interface, we wanted the user experience to be as smooth and intuitive as possible. The idea is that anyone can open the application and start playing without needing instructions.

Key UX ideas:

  • Visual Feedback: The interface reacts immediately to user actions (valid moves, turn changes, errors). This helps players understand what is happening in the game at all times.

  • Consistency: We try to keep the same style and interaction patterns across all pages so the user does not have to "relearn" how to use the app.

  • Accessibility: Clear colors, readable text, and simple layouts help make the game accessible to a wider audience.

8.3. Safety and Security Concepts

Security is a cross‑cutting concern because it affects both the backend services and the frontend. Even though this is an academic project, we still apply basic security principles.

  • API Gateway Isolation: Nginx acts as the single public entry point, isolating backend services from direct external access. Only Nginx is exposed on port 80, while all backend services run on internal Docker network ports.

  • Authentication and Authorization: Only authenticated users can access certain features, such as creating matches or viewing their profile. The Auth Service handles JWT authentication and exposes token verification for internal service-to-service authorization checks.

  • Password Protection: Passwords are never stored in plain text. Instead, they are hashed and salted before being saved in the database using bcrypt or Argon2.

  • Data Protection: We only store the minimum amount of user information needed for the system to work. Data is split by domain with strict ownership: auth.db is accessible only through Auth Service, users.db only through Users Service, and game.db only through Game Service.

  • CORS and Rate Limiting: Nginx handles Cross-Origin Resource Sharing (CORS) headers and rate limiting to prevent abuse and ensure legitimate API usage.

8.4. Architecture and Design Patterns

The system follows a microservice-based architecture. Instead of having one big monolithic application, we split the project into several smaller, independent services:

  • React Frontend (webapp/): The user interface running in the browser. It is a single-page application (SPA) built with React, Vite, and TypeScript. The frontend manages UI state and coordinates API calls to backend services.

  • Nginx: The API Gateway and reverse proxy that routes requests to appropriate backend services. It handles CORS, rate limiting, WebSocket proxying for Socket.IO, and serves the React frontend static files.

  • Auth Service (auth/): Node.js + Express + TypeScript service that manages registration, authentication (JWT access/refresh lifecycle), and internal token verification. It is the single point of access to auth.db.

  • Users Service (users/): Node.js + Express + TypeScript service that manages user profile data. It is the single point of access to users.db.

  • Game Service (gameservice/): Node.js + Express + TypeScript service that manages match persistence, move history, player statistics, online matchmaking, and real-time sessions via Socket.IO. It is the single point of access to game.db and Redis.

  • Game Server (gamey/): Rust service that implements the Y‑game engine and bot logic. It is stateless and focuses purely on game rules, move validation, and bot strategies.

Some patterns we use:

API Gateway Pattern: Nginx centralizes all external requests and routes them to the appropriate service based on URL paths (/api/auth/, /api/users/, /api/game/, /api/gamey/, /).

Repository Pattern: Each backend service uses the Repository pattern to abstract database access, making it easier to test and maintain.

Strategy Pattern: The Game Server uses the Strategy pattern to implement multiple bot difficulty levels (random, heuristic, neural network) in an extensible way.

Observer Pattern (via Socket.IO events): The Game Service uses Socket.IO events to notify all connected clients of board state changes in real time, decoupling the session state update from the client rendering logic.

Separation of Concerns: The frontend separates UI components, state management (React hooks/context), and API call logic into distinct layers.

8.5. Development Concepts

These concepts describe the practices that the whole team follows during development.

Version Control:

The project is developed collaboratively using GitHub. Branching, pull requests and code reviews ensure that all components evolve consistently.

Testing:

Each part of the system includes automated tests:

  • Rust Game Server: unit tests for game logic and strategies

  • Users Service (Node.js): API endpoint tests and integration tests

  • Auth Service (Node.js): unit and integration tests for authentication flows

  • Game Service (Node.js): unit and integration tests for match, matchmaking, and online session logic

  • React Frontend: component tests and end-to-end tests

Testing is transversal because it guarantees correctness across all services.

Continuous Integration:

GitHub Actions automatically run builds and tests whenever code is pushed. This helps detect errors early and keeps all services aligned.

8.6. Persistence Layer

The application relies on two complementary persistence mechanisms split by domain and lifetime:

Durable storage (SQLite):

Three independent SQLite databases store long-lived data:

  • auth.db — accessible only through Auth Service. Stores user credentials and JWT token metadata.

  • users.db — accessible only through Users Service. Stores user profile data.

  • game.db — accessible only through Game Service. Stores match records, move history, and player statistics.

Each database is accessed exclusively by its owning service. This design decision ensures data consistency, security, and separation of concerns.

Ephemeral storage (Redis):

Redis is used exclusively by the Game Service for data that is short-lived and must be accessed with low latency:

  • Matchmaking queue: stores the list of players waiting for an online opponent. Entries are created when a player joins and removed when matched or when the timeout expires.

  • Online session snapshots: stores the current board state, active turn, and player list for each ongoing online match. This allows the Game Service to process moves and broadcast updates without hitting game.db on every turn.

  • Socket.IO pub/sub adapter: Redis acts as the message broker between Socket.IO server instances, ensuring events are delivered to all connected clients even if multiple Game Service replicas are running.

Redis data is ephemeral and requires no host volume. If Redis restarts, ongoing online sessions are lost, but durable match history in game.db is unaffected.

8.7. Error Handling

The application includes a consistent error handling strategy to deal with the different types of errors that may appear during execution. The goal is to detect problems early, avoid unexpected crashes and provide clear feedback both to developers and to users.

At the user level, the application only displays simple and understandable messages, without exposing technical details. This prevents confusion and avoids leaking internal information. On the other hand, the backend services and the Rust game engine generate structured error messages and log them internally so developers can identify the cause of the problem more easily.

Error Handling Across Services:

  • React Frontend: Displays user-friendly error messages for API failures, invalid moves, or authentication errors. Uses try-catch blocks and error boundaries for React component errors.

  • Nginx: Returns standard HTTP error codes (400, 401, 404, 500) and logs errors for monitoring.

  • Auth Service: Validates input, handles database errors, and returns appropriate HTTP status codes with error messages. Uses middleware for centralized error handling.

  • Users Service: Validates input, handles database errors, and returns appropriate HTTP status codes with error messages. Uses middleware for centralized error handling.

  • Game Service: Uses typed domain errors (MatchNotFoundError, UnauthorizedMatchError, InvalidMoveError) mapped to HTTP status codes by a global error handler middleware. Socket.IO errors are emitted as structured events to the affected client.

  • Game Server: Returns structured error responses for invalid YEN notation, illegal moves, or computation failures. Rust’s Result type ensures explicit error handling.

9. Architecture Decisions

9.1. ADR 1: Microservices Architecture

  • Context: The platform is expected to scale with increasing user demand, evolve over time with new features or game modes, and support parallel development by multiple team members. High availability and maintainability are important non-functional requirements.

  • Status: Accepted by the teacher and development team.

  • Possible alternatives:

    • Monolithic architecture: where all game logic, API web and data storage services would be integrated in a single backend app. Although it would have simplified the development at first, this solution presents big issues in terms of scalability and maintainability.

  • Decision: We decided to implement a microservice-based architecture with a clear separation between game logic, data persistence, and presentation layers. The system will consist of:

    • Game Server (Rust) handling core game rules, move validation, and bot strategies

    • Auth Service (Node.js + Express + TypeScript) managing user authentication, registration, and JWT token lifecycle, with exclusive access to auth.db

    • Users Service (Node.js + Express + TypeScript) managing user profile data, with exclusive access to users.db

    • Game Service (Node.js + Express + TypeScript) managing match persistence, move history, statistics, online matchmaking, and real-time sessions, with exclusive access to game.db and Redis

    • React Frontend (Vite + TypeScript) for user interaction, running entirely in the browser and coordinating API calls to backend services

    • Nginx API Gateway routing requests and handling cross-cutting concerns (CORS, rate limiting, WebSocket proxying, security)

    • Each service owns its own data store, ensuring fault isolation at the data layer
      This approach allows each component to evolve independently while using the most appropriate technology for its responsibilities.

  • Pros:

    • Clear separation of concerns between game logic, data persistence, and user interface

    • Enables independent development and deployment of all services

    • Each data store is isolated per domain, ensuring fault isolation

    • Nginx provides security isolation for backend services

  • Cons:

    • Increased architectural complexity compared to a single monolithic backend

    • Introduces inter-service communication overhead and potential latency

    • Frontend must coordinate multiple API calls for complex workflows

9.2. ADR 2: Rust for Game Server

  • Context: The game server must be performant, safe, and reliable, as errors in the game logic could compromise fairness or stability. Additionally, the engine may need to handle multiple concurrent games and interact efficiently with other services in the system.

  • Status: Accepted by the teacher and development team.

  • Possible alternatives:

    • There were no possible alternatives because this was an imposed constraint, given by the course’s teachers.

  • Decision: We decided to implement the Game Server using Rust, which provides strong compile-time guarantees for memory safety and concurrency without relying on a garbage collector. This makes it well-suited for implementing complex game logic that must be both efficient and robust. The Game Server will be completely stateless, with no database access. All persistence is delegated to the Game Service.

  • Pros:

    • Strong memory safety guarantees, reducing runtime errors and crashes

    • High performance and excellent support for concurrency, which is important for managing game sessions in parallel

    • Stateless design enables horizontal scaling

  • Cons:

    • Steeper learning curve, as the development team is not familiar with this programming language

9.3. ADR 3: Docker Containerization

  • Context: The platform is composed of multiple independent components implemented with different technologies and runtimes. A way to ensure consistent development, testing, and deployment across different environments is essential to reduce configuration issues and simplify setup for new team members.

  • Status: Accepted by the teacher and development team.

  • Possible alternatives:

    • There were no possible alternatives because this was an imposed constraint, given by the course’s teachers.

  • Decision: We decided to containerize all major components of the system using Docker. Each service (React Frontend, Nginx, Auth Service, Users Service, Game Service, Game Server, Redis, Prometheus, Grafana) will run inside its own Docker container, with Docker Compose used to orchestrate and manage the multi-container setup. This ensures environment consistency and simplifies deployment and execution of the full system.

  • Pros:

    • Provides consistent environments across development, testing, and deployment

    • Simplifies setup and onboarding by reducing manual configuration

    • Enables easier deployment and scaling of individual services

    • Isolated network for backend services through Docker networking

    • Each database file is mounted as an independent volume per service container

  • Cons:

    • Adds an extra layer of complexity to the development workflow

    • Requires basic knowledge of Docker and container orchestration from the development team

9.4. ADR 4: SQLite as Database System

  • Context: The application requires persistent storage for game data, user information, and match states. The database solution should be simple to operate, easy to integrate with the existing backend services, and appropriate for an academic project deployed in a cloud environment.

  • Status: Proposed by the development team.

  • Possible alternatives:

    • MongoDB: A document-oriented NoSQL database was considered. It would allow the development team to store game states in a more flexible way, using JSON structures. This alternative was not adopted as the development team was not familiar with this kind of DBs when the decision was taken.

  • Decision: We decided to use SQLite as the database system for all three data domains. Each service owns its own SQLite database file (auth.db, users.db, game.db), mounted as an independent Docker volume. No service has access to another service’s database. This choice is also highly based on the development team’s experience using relational databases.

  • Pros:

    • Easy integration with backend services and containerized environments

    • Suitable for small-to-medium workloads

    • Low learning curve, as the development team is used to working with relational databases

    • Each service owns its own database file, enforcing data isolation

    • Embedded database requires no separate server process

  • Cons:

    • Limited scalability and concurrency support compared to cloud-native database solutions

    • Not ideal for high-availability or horizontally scaled write-heavy workloads

9.5. ADR 5: Nginx as API Gateway

  • Context: The system requires a secure entry point for all external requests. Backend services (Auth Service, Users Service, Game Service, and Game Server) should not be directly accessible from the internet. Additionally, cross-cutting concerns such as CORS, rate limiting, and request routing need to be handled consistently.

  • Status: Proposed by the development team.

  • Possible alternatives:

    • Direct access to backend services: Each service could expose its own public port, but this would increase security risks and make it harder to manage cross-cutting concerns.

    • Cloud API Gateway services (AWS API Gateway, Azure API Management): These would provide more features but add complexity and cost for an academic project.

  • Decision: We decided to use Nginx as a reverse proxy and API Gateway. Nginx will be the only component with a publicly exposed port (80). It will route requests based on URL paths:

    • /api/auth/* → Auth Service (port 3001, internal)

    • /api/users/* → Users Service (port 3000, internal)

    • /api/game/* → Game Service (port 3002, internal), including WebSocket upgrade for Socket.IO

    • /api/gamey/* → Game Server (port 4000, internal)

    • /* → React Frontend static files Nginx will also handle CORS headers, rate limiting, WebSocket proxying, and request logging.

  • Pros:

    • Single public entry point enhances security (defense in depth)

    • Backend services are isolated on internal Docker network

    • Centralized handling of CORS, rate limiting, WebSocket proxying, and logging

    • High performance and battle-tested in production environments

    • Simple configuration and widely known by the development community

  • Cons:

    • Adds an additional component to the system architecture

    • Requires learning Nginx configuration syntax

    • Single point of failure (mitigated by its reliability)

9.6. ADR 6: Three Independent Databases for Fault Isolation

  • Context: The initial architecture used a single SQLite database accessed exclusively by the Users Service. As the system evolved and the number of concurrent users and game sessions grew, a concern arose: a single overloaded or failed database would bring down all data-dependent functionality simultaneously — authentication, user profiles, and game data would all fail together.

  • Status: Accepted by the teacher and development team.

  • Possible alternatives:

    • Single database: One SQLite file accessed by one service. Simple but creates a single point of failure at the data layer. If the database is locked or overloaded (e.g., by a high volume of match writes), authentication also fails.

    • Single service with multiple internal connections: A single backend service managing three internal database connections. This keeps one entry point but does not achieve true fault isolation at the service level — if the service crashes, all three databases become inaccessible anyway.

  • Decision: We decided to split the data layer into three independent services, each owning its own SQLite database:

    • Auth Serviceauth.db: stores credentials and JWT token metadata

    • Users Serviceusers.db: stores user profile data

    • Game Servicegame.db: stores matches, move history, and statistics Each database is mounted as an independent Docker volume, and no service has access to another service’s database.

  • Pros:

    • Fault isolation: a failure or overload in game.db (e.g., caused by a high volume of concurrent matches) does not affect authentication or user profile access

    • Independent scalability: each service and its database can be scaled according to its own load profile

    • Single responsibility: each service has a clearly defined data domain, improving maintainability

  • Cons:

    • Queries that previously joined data across domains (e.g., linking statistics to a user profile) now require inter-service HTTP calls, adding latency and complexity

    • Three services to maintain instead of one, increasing development and operational overhead

    • Cross-service consistency (e.g., deleting a user requires coordinating Auth Service, Users Service, and Game Service) must be handled at the application level

9.7. ADR 7: Redis and Socket.IO for Online Matchmaking and Real-time Sessions

  • Context: Adding online human-vs-human matches required two capabilities that the existing architecture could not provide efficiently: (1) a low-latency shared queue to pair players waiting for an opponent, and (2) a real-time bidirectional communication channel to propagate board state updates to both players instantly after each move. Polling a REST endpoint for both purposes would introduce unacceptable latency and unnecessary load on game.db.

  • Status: Proposed by the development team.

  • Possible alternatives:

    • REST polling only (no Redis, no WebSocket): Both matchmaking and move synchronisation would be handled by clients polling REST endpoints against game.db. Simple to implement, but introduces high latency for move propagation, increased database load, and a poor real-time user experience.

    • Long polling / Server-Sent Events (SSE): A lighter alternative to WebSocket for server-to-client push. Would reduce polling overhead but does not support bidirectional communication natively, complicating turn submission and event acknowledgment.

    • In-memory matchmaking queue (no Redis): A plain Node.js in-memory queue instead of Redis. Simpler to set up, but the queue is lost on service restart and does not support multiple Game Service instances sharing the same queue state.

  • Decision: We decided to introduce Redis as an ephemeral in-memory store and Socket.IO as the real-time communication layer, both exclusively used by the Game Service:

    • Redis stores the matchmaking queue entries and active online session snapshots (board state, current turn, player list). It also acts as the @socket.io/redis-adapter pub/sub backend, ensuring Socket.IO events are delivered correctly even if multiple Game Service instances are running.

    • Socket.IO provides a WebSocket-based bidirectional channel between the React Frontend and the Game Service. After each move, the Game Service broadcasts the updated board state to all players in the session. Turn timeout events and bot fallback notifications are also delivered via Socket.IO.

    • Redis runs as a separate Docker container (port 6379, internal only) with no host volume, as its data is fully ephemeral.

    • Nginx proxies the Socket.IO WebSocket connection transparently under /api/game/socket.io/.

  • Pros:

    • Low-latency real-time updates: board state changes are pushed to clients instantly via WebSocket instead of requiring polling

    • Redis decouples matchmaking queue state from game.db, avoiding write contention under high concurrency

    • The @socket.io/redis-adapter enables horizontal scaling of the Game Service without losing session events

    • Turn timeout and bot fallback logic integrates naturally with Socket.IO event emission

    • Redis failure only affects online matchmaking and active sessions; AI matches, authentication, and statistics remain fully operational

  • Cons:

    • Adds two new components (Redis and Socket.IO) to the architecture, increasing operational complexity

    • Online session state is ephemeral: a Redis restart causes active online sessions to be lost (ongoing match state cannot be recovered without additional persistence logic)

    • Nginx must be configured to handle WebSocket upgrades correctly for the Socket.IO path

    • Debugging real-time event flows is more complex than tracing synchronous REST calls

10. Quality Requirements

This project is mainly focused on 4 basic quality attributes for an online game as GameY is. Those are good performance, high usability, easy maintainability and strong security.

10.1. Quality Tree

quality tree

10.2. Quality Scenarios

Attribute Description Metric

Response time

Time the system takes to react after a user’s abstraction

The system should respond to user actions within 1 second

Currency

System’s capacity to support different games and users at the same time

The system should support at least 25 simultaneous games or users without noticeable lag

Intuitiveness

Ease with which players can understand and play the game in a short period of time

New players should be able to understand and play the game within 10 minutes.

Continuity

System’s capacity to keep games and sessions active, keeping their progress updated

At least 90% of games and sessions should maintain their progress after interruptions.

Modularization

Organizational quality, where different functionalities of the program are located in different modules, so that changes in one part do not affect others

The program should be organized in at least 3 independent modules to isolate changes

API access

Secure control to the game funcitionalities, avoiding non authorized uses of the game

All unauthorized API requests should be blocked to prevent misuse

User’s information

Users' data protection, granting privacy and integrity.

User critical data should be properly protected and encrypted

11. Risks and Technical Debts

Contents

A list of identified technical risks or technical debts, ordered by priority.

We identified some technical risks that can affect the development of the Y game project. These risks must be identified and registered, and we can mitigate or accept them.

11.1. Risks

Risks can appear like part of Quality Attributes scenarios. We categorized the next ones based on the risk criteria of the project.

Table 1. Risk table
Area (Risk Criterion) Brief Description Mitigation measures Probability (1-3) Impact (1-3) Total Risk

Security

The project needs to pass strict security tests. There is a risk to not complete these requirements when we implement the backend and expose vulnerabilities in the game or the connection.

Do early validations, use security tools integrated in the Rust compiler (cargo audit) and make exhaustive code reviews.

2

2

4

Data Integrity

The system now uses three independent databases (auth.db, users.db, game.db). Operations that span multiple services (e.g., deleting a user requires coordinating Auth Service, Users Service, and Game Service) must be handled at the application level without native transaction support across services. This increases the risk of partial failures leaving data in an inconsistent state.

Implement compensating operations for cross-service data changes. Define clear ownership rules per data domain. Add integration tests that verify consistency across services after multi-step operations.

2

3

6

Performance

The game engine in Rust must allow to play against a bot with different strategies. If the decision algorithms of the robot are not optimized, the response time can degrade the experience of the user.

Design efficient AI strategies, measure times of execution (benchmarking) in the Rust engine from the first sprints and profile the code if there is bottlenecks.

2

2

4

Inter-service Communication Complexity

The separation into three independent backend services (Auth Service, Users Service, Game Service) introduces inter-service HTTP calls for operations that previously were internal (e.g., JWT verification, linking statistics to user profiles). This adds latency, increases the number of potential failure points, and complicates local development and debugging.

Define clear and versioned internal API contracts between services. Use Docker Compose to simplify local multi-service orchestration. Implement timeouts and fallback strategies for inter-service calls. Add end-to-end tests that cover cross-service flows.

2

2

4

Unknowns

Lack of deep knowledge in the ownership model of Rust and the typing of TypeScript. Sometimes we don’t have enough information about how an architecture can satisfy the requirements.

Dedicate initial time to Proof of Concepts (PoC), read official documentation and establish pair programming to level the knowledge of the team.

3

3

9

11.2. Technical debts

Technical debt Brief description

External API Dependency

Our app depends a lot of external services (like map services or data APIs). If those services change their API or go down, parts of our app will stop working. Right now, we don’t have a good abstraction layer to swap those services easily.

Network Inconsistency

The performance of the app depends directly on how good the user’s internet connection is. We still haven’t implemented a solid offline mode or cache system (like Service Workers), so with a bad connection the user experience will be very bad.

Monolithic Configuration

Because we don’t have much experience with server deployment, our first configuration will probably be a bit hardcoded or less modular than it should be. Later we need to refactor this to move to a more scalable setup using environment variables.

Cross-service Data Consistency

Operations that affect multiple data domains (e.g., user deletion, which must remove records from auth.db, users.db, and game.db) currently have no distributed transaction mechanism. This is accepted as a technical debt for the academic scope of the project and should be addressed with compensating transactions or a saga pattern in a production-grade system.

12. Glossary

Term Definition

API Gateway

Architectural component that serves as the single public entry point, routing client requests to backend services and managing cross-cutting concerns such as CORS, rate limiting, and WebSocket proxying

Nginx

Web server and reverse proxy used to implement the API Gateway pattern in this system

Anonymous Play

Game mode where unregistered users can play against the AI without persisting their statistics or match history

Arc42

Architecture documentation template followed in this project for structural consistency

Barycentric Coordinates

Coordinate system (x, y, z) used to represent positions on the hexagonal game board

Board

Hexagonal grid representing the Y game playing surface

Bot Client

External automated agent that plays through the system’s Bot API

Bot Fallback

Mechanism triggered by BotFallbackService when a player does not respond within the allowed turn time (TURN_TIMEOUT_SEC) or when no human opponent is found during matchmaking (MM_TIMEOUT_SEC). The Game Service calls the Rust Game Server to compute and apply a move automatically on the inactive player’s behalf.

CORS (Cross-Origin Resource Sharing)

HTTP-header based mechanism that allows a server to indicate which origins are permitted to access its resources from a browser

Docker

Containerization platform used for packaging and deploying all system services

Game Server

Rust-based microservice responsible for core game logic, move validation, and bot AI strategies. Completely stateless and has no database access.

Game Service

Node.js microservice responsible for match persistence, move history, player statistics, online matchmaking, and real-time session management via Socket.IO. Single point of access to game.db and Redis.

Match

Game session between two players (human or bot) with a specific board size and strategy configuration

Matchmaking

Process by which the Game Service pairs two human players waiting in the Redis-backed queue to start an online session. If no opponent is found within MM_TIMEOUT_SEC, a bot is assigned as the opponent.

Microservices

Distributed architecture pattern where the system is decomposed into independent services (React Frontend, Nginx, Auth Service, Users Service, Game Service, Game Server), each owning its own data store

Move/Movement

Player action placing a piece at specific coordinates on the board

Online Session

A real-time multiplayer match between two human players (or a human and a bot fallback), managed by the Game Service via Socket.IO. Session state is stored in Redis for low-latency access during play.

PlantUML

Tool used for generating architectural diagrams throughout the documentation

Redis

In-memory data store used exclusively by the Game Service to persist the matchmaking queue and active online session snapshots, and to act as the pub/sub adapter for Socket.IO

Registered Player

User with an account who can access match history, statistics, and online multiplayer features

Reverse Proxy

Server (Nginx) that forwards client requests to appropriate backend services and returns responses

Socket.IO

Library providing WebSocket-based bidirectional communication between the React Frontend and the Game Service. Used to broadcast real-time board state updates, turn notifications, timeout events, and game-over signals to all players in an online session.

SQLite

Embedded relational database used for durable persistence of credentials (auth.db), user profiles (users.db), and match data (game.db)

Stateless Service

Service that does not maintain persistent state between requests (e.g., Game Server). All state is passed in each request or stored externally.

Strategy

AI difficulty level and bot behavior configuration (e.g., random, heuristic, neural network)

Turn Timeout

Maximum time (TURN_TIMEOUT_SEC) a player has to submit a move during an online session. If the timer expires, TurnTimerService fires and BotFallbackService plays automatically on the player’s behalf.

Upstream

Nginx configuration directive defining a group of backend servers that can handle requests (e.g., users_backend, gamey_backend)

Users Service

Node.js microservice responsible for managing user profile data. Single point of access to users.db.

WAL (Write-Ahead Logging)

SQLite mode that enables concurrent reads during writes, improving database performance under load

Win Checker

Algorithm that detects Y-shaped winning connections on the board to determine game outcome

YBot

Rust trait/interface defining the contract for bot strategy implementations

YEN (Y-Encoded Notation)

Domain-specific format for representing board positions and moves in the Y game, used for inter-service communication between the React Frontend, Game Service, and Game Server

YGN (Y Game Notation)

Alternative notation format supported by the Game Server for recording complete game sequences, used for match replay and history export