Разработка ММО (часть 4) - архитектура
Ну конечно, на четвёртой статье самое то писать про архитектуру проекта. Как раз, чёрт побери, вовремя 😁 Но, как говорится, лучше поздно, чем никогда.
Напомню, я делаю Tranza Online — симулятор транспортной компании. Игрок начинает с одной машины, выполняет заказы, зарабатывает деньги, покупает новый транспорт, обслуживает парк, строит компанию и постепенно разрастается до более серьёзного бизнеса.
На старте всё казалось достаточно простым. Есть игра, есть backend, есть база данных, есть небольшой сайт-лендинг. Ну и что тут может пойти не так?
Изначально архитектура была максимально простой:
Есть два контура: dev и prod.
Дев поднят локально на Linux-машине в Docker. Там крутится backend, база, сайт и всё, что нужно для разработки. Настроен автодеплой из PhpStorm, чтобы не пересобирать контейнеры руками после каждого чиха.
Прод живёт отдельно. Изменения выкатываются через GitHub Actions при пуше в master. Ничего сверхъестественного: собрали, доставили, перезапустили нужные сервисы.
Сайт на тот момент был просто лендингом на чистом HTML. Он никак не влиял на игровой backend, ничего не знал про игровые данные, ни с кем особо не разговаривал. Просто показывал информацию об игре, а рядом лежали файлы для скачивания.
И для такого сценария эта архитектура была нормальной.
Проблемы начались ровно в тот момент, когда сайт перестал быть просто страничкой.
Я решил доработать лендинг, который до этого был завайбокожен примерно за один запрос, и превратить его во что-то более-менее внятное:
регистрация и авторизация;
подтверждение почты;
восстановление пароля;
личный кабинет;
новости;
детальные страницы новостей;
дневник разработки;
список игровых компаний пользователя;
служебные уведомления;
минимальная админка.
И тут сразу встал вопрос: а где всё это хранить?
Первый очевидный вариант — просто добавить новые таблицы в существующую базу Game API.
Технически так сделать можно. Особенно на старте. Более того, это самый быстрый путь.
Но проблема в том, что backend игры в таком случае начинает отвечать вообще за всё:
игровую экономику;
транспорт;
компании;
заказы;
прогресс игрока;
авторизацию на сайте;
подтверждение почты;
восстановление паролей;
новости;
дев-лог;
личный кабинет;
админку;
рассылки;
служебные уведомления.
И получается не backend игры, а целый комбайн с гордым названием "потом разберёмся".
А "потом" обычно наступает в момент, когда уже больно.
Поэтому я решил разделить сайт и игровую часть хотя бы на уровне доменов и ответственности.
Новая схема для каждого стенда выглядит примерно так:
То есть теперь сайт — это не просто папка с HTML-файлом, а отдельная часть продукта.
Site Frontend отвечает за публичные страницы:
главная;
новости;
детальная страница новости;
дневник разработки;
страница авторизации;
личный кабинет;
дорожная карта.
Site Backend отвечает за всё, что относится именно к сайту:
регистрацию;
вход;
подтверждение почты;
восстановление пароля;
сессии или токены;
новости;
дев-лог;
отправку писем;
пользовательские настройки сайта;
запрос краткой игровой информации из Game API.
Site Database хранит данные сайта:
пользователей сайта;
email-токены;
password reset-токены;
новости;
записи дев-лога;
настройки профиля;
служебные данные личного кабинета.
Game API остаётся отдельным и занимается только игрой:
игровые компании;
транспорт;
гаражи;
заказы;
маршруты;
экономика;
топливо;
ремонт;
обслуживание;
прогресс;
внутриигровое время;
будущая MMO-логика.
То есть сайт не должен знать, как рассчитывается доход автомобиля за игровой день. А Game API не должен знать, как сверстать страницу новости или отправить письмо для восстановления пароля.
Это разные причины для изменений. А если причины для изменений разные — лучше не держать всё в одной куче.
Отдельная боль — авторизация.
Сейчас есть соблазн сделать так, чтобы один бэк отвечал и за регистрацию и вход Game API и хранил игровых пользователей и игровые данные. Вот только важно не напороться на дублирование пользователей. Если сайт регистрирует пользователя, а Game API тоже хранит пользователя, нужно сразу решить: кто является источником истины?
То есть пользователь как аккаунт живёт на стороне сайта/авторизации.
А Game API хранит не “пользователя сайта”, а игровой профиль, привязанный к userId.
Условно:
Сайт может спросить у Game API:
GET /profiles/me GET /companies/my GET /companies/{id}/summary
И показать в личном кабинете, например:
какие компании есть у пользователя;
сколько всего транспорта;
какой баланс;
когда была последняя активность;
есть ли активные заказы.
Но управлять игровыми сущностями сайт не должен. Он только показывает краткую информацию. Все действия с игрой остаются внутри игры и Game API.
Отдельный Auth-сервис, конечно, очень просится, тогда и сайт, и игра, и админка могли бы ходить в один центр авторизации. Но я стараюсь не устраивать микросервисный зоопарк раньше времени. Поэтому на первом этапе авторизация может жить внутри Site Backend, но проектироваться так, чтобы потом её можно было вынести отдельно без ритуального сожжения всего проекта.
Примерно так:
То есть главное — не физическое разделение с первого дня, а нормальные границы внутри кода.
То же самое с кэшем и очередями.
Можно спросить: "Зачем тебе cache и queue на таком этапе?"
Справедливый вопрос.
Кэш нужен не потому, что у меня уже миллионы игроков и сервер задыхается. Пока нет. Кэш нужен для нормальной инфраструктуры вокруг сайта и игры:
хранить временные токены;
ограничивать частоту запросов;
кэшировать публичные новости;
хранить короткоживущие данные;
ускорять часто запрашиваемые summary-данные;
не дёргать игровую базу там, где можно не дёргать.
Очередь нужна для фоновых задач:
отправить письмо подтверждения;
отправить письмо восстановления пароля;
обработать уведомление;
опубликовать новость;
пересчитать служебные данные;
подготовить статистику;
в будущем — обрабатывать игровые события.
Например, пользователь зарегистрировался.
Пользователь не должен ждать, пока где-то там отправится письмо.
Плюс очередь пригодится и в игре. Например, если потом появятся фоновые игровые события, уведомления, пересчёты, обработка завершённых заказов или отложенные операции.
Админку тоже решил заложить сразу, хотя бы в минимальном виде.
Не огромную корпоративную панель управления космическим кораблём, а простой инструмент, чтобы не лазить руками в базу.
На старте админка может уметь:
создавать и редактировать новости;
публиковать записи дневника разработки;
смотреть пользователей;
смотреть игровые профили;
видеть список компаний;
проверять служебные статусы;
включать/отключать публикации;
смотреть базовые логи.
Без админки всё это обычно заканчивается SQL-запросами руками. А SQL-запросы руками на проде — это отдельный вид эротического искусства.
В итоге целевая архитектура сейчас выглядит примерно так:
Физически это не обязательно должно быть десять разных серверов. На старте всё может спокойно жить на одной машине в Docker.
Важнее другое: логически разделить зоны ответственности.
Сайт отвечает за сайт.
Game API отвечает за игру.
Админка отвечает за управление.
Очередь отвечает за фоновые задачи.
Кэш отвечает за быстрые временные данные.
База игры не превращается в склад всего подряд.
Понятно, что это всё ещё не финальная архитектура. Если проект доживёт до полноценного онлайна и MMO-механик, там появятся новые вопросы:
отдельный Auth-сервис;
матчинг или распределение игровых серверов;
обработка игровых событий;
античит;
аудит операций;
внутриигровая экономика;
очереди событий;
аналитика;
мониторинг;
централизованные логи;
миграции;
резервное копирование;
rate limiting;
разграничение прав;
модерация.
Но пытаться решить всё это прямо сейчас — верный способ не выпустить вообще ничего.
Поэтому текущая цель проще: сделать архитектуру не идеальной, а такой, чтобы через полгода не хотелось удалить репозиторий и уйти выращивать огурцы.
На старте, на мой взгляд, разумным будет такой компромисс:
Не делать огромный монолит, где сайт, игра и админка перемешаны.
Не делать полноценные микросервисы ради микросервисов.
Разделить домены и базы там, где это действительно имеет смысл.
Оставить возможность вынести Auth в отдельный сервис позже.
Сразу заложить очередь и кэш, но использовать их без фанатизма.
Держать Game API максимально чистым от логики сайта.
Возможно, я где-то перемудрил. Возможно, наоборот, где-то ещё недодумал.
Поэтому если у вас есть предложения, возражения или желание сказать “да зачем тебе всё это, сделай один Laravel-монолит и не страдай” — добро пожаловать в комментарии.
Архитектура, как известно, лучше всего проектируется коллективным спором в интернете.








