Проектирование сущности GameSession для игровой платформы
При разработке бэкенда для платформы с мини-играми ключевой задачей является управление игровыми сессиями. Сущность GameSession отслеживает каждую игровую попытку пользователя: начало, конец, истечение срока. В этой статье мы разберём оптимальную архитектуру, обязательные поля, публичный API и механизмы синхронизации.
Множественные активные сессии: допустимо ли?
Допускается ли несколько открытых сессий с одинаковым game_id и user_id? В большинстве игровых платформ - да, если пользователь играет с разных устройств или вкладок. Однако возможны три подхода:
- Разрешить множественные сессии - гибко, но сложнее в аналитике и может привести к дублированию действий.
- Закрывать предыдущую открытую сессию при новом /start-game - просто, но может прервать игру на другом устройстве.
- Возвращать токен существующей открытой сессии - предотвращает дубли, но требует проверки статуса на клиенте.
Рекомендуется первый вариант с дополнительной валидацией на стороне клиента (например, подтверждение переключения устройства).
sessionToken или id в публичном API?
Используйте sessionToken (UUID) вместо внутреннего id в открытых эндпоинтах. Это повышает безопасность: злоумышленник не сможет угадать последовательные ID. Кроме того, sessionToken может быть переиспользован для аутентификации сессии в WebSocket или long-polling.
Обязательные поля GameSession
Для корректного управления состоянием сессии необходимы как минимум:
id- первичный ключ (UUID).userиgame- внешние ключи.sessionToken- уникальный публичный идентификатор.status- enum: OPEN, CLOSED, EXPIRED.startedAt- время начала (автоматическое).expiresAt- время автоматического истечения.endedAt- время завершения (nullable).
Дополнительно можно добавить score, duration или metadata (JSONB) для гибкости.
Нужна ли блокировка при завершении сессии?
Да, чтобы избежать двойного завершения или гонок. Используйте оптимистичную блокировку (версионирование записи) или пессимистичную блокировку (SELECT ... FOR UPDATE) в зависимости от нагрузки. Например, при вызове /end-game проверяйте статус и обновляйте его атомарно:
UPDATE game_sessions SET status = 'CLOSED', endedAt = NOW() WHERE id = :id AND status = 'OPEN';Если затронуто 0 строк - значит сессия уже закрыта или истекла.
Оптимальное время expiresAt
Определите expiresAt на основе типа игры:
- Для быстрых мини-игр (головоломки) - 5-15 минут.
- Для стратегий или пошаговых игр - 30-60 минут.
- Для игр с длительными сессиями - до 24 часов.
Настройте cron-задачу для пакетного перевода просроченных сессий в статус EXPIRED. Также можно добавить WebSocket-уведомление клиенту об истечении.
Best practices для архитектуры игровой платформы
- Используйте REST API для команд (start/end) и WebSocket для стриминга состояния.
- Храните sessionToken в HTTP-only cookie или заголовке Authorization.
- Добавьте rate limiting на /start-game и /end-game, чтобы избежать флуда.
- Логируйте все изменения статуса сессии для отладки и аналитики.
- Рассмотрите материализованные представления для подсчёта активных игроков.
Примеры архитектур: репозитории мини-игр на GitHub и документация по очередям в NestJS для обработки завершения сессий.