В 2015 году, разрабатывая браузерную онлайн-игру, мы столкнулись со следующей задачей: сделать второй сервер с другими настройками и механиками игры, а также вести перспективную разработку новой версии игры, в которой будет еще больше изменений, при этом общий каркас у всех версий игры одинаковый. Дело осложняется тем, что игре требуются постоянные обновление и исправления, т.к. игровой процесс идет и останавливать его из-за разработки параллельной версии никак нельзя.
Мы используем SVN в качестве системы контроля версий, и теория гласит, что в нашем случае резонно делать ветки (branch), а потом их хитро мержить. Ну или делать fork продукта в новый репозиторий, и развивать новую версию отдельно. Но мы пошли своим путем, который чертовски удобен, и может быть полезен читателям, о нем и мой рассказ.
Шаг 1. Берем статическую таблицу в БД (state), где хранятся разные параметры состояния сервера, например игровая дата, и добавляем поле server_type
Шаг 2. Делаем статически методы, которыми будем определять тип сервера
Шаг 3. Готовим методы для веб-интерфейса, чтобы определять тип сервера:
Шаг 4. Во все места бизнес-логики вставляем блоки типа:
Шаг 5. Во все места веб-интерфейса вставляем проверку на доступность элемента интерфейса.
Тут надо сделать поправку на то, что мы используем самописный фреймворк, который позволяет скрывать элементы по двум видам правил:show/hide — где непосредственно вычисляется значение какого поля, allow/deny — тогда у пользователя, который залогинен запрашиваются права на какие-то действия. Последнее правило дает возможность вести разработку прямо в основной ветке репозитория, просто скрывая новый функционал правами например:
Развивая тему дальше можно делать аналогичным образом сплит тестирование механик в разных частях игрового мира просто подменяя имплементацию старой логики на новую.
Своего рода dependency injection, однако есть неоспоримые преимущества за счет гибкого и прозрачного варианта настроек алгоритма.
Код получается немного пухленький, зато имеет минимальные затраты на поддержку всей системы, т.к. фактически 1 флажок в БД менять логику работы сервера. Опыт показал, что особых проблем такое сожительство версий не вызывает, а перспективные разработки — заметно ускоряются.
Мы используем SVN в качестве системы контроля версий, и теория гласит, что в нашем случае резонно делать ветки (branch), а потом их хитро мержить. Ну или делать fork продукта в новый репозиторий, и развивать новую версию отдельно. Но мы пошли своим путем, который чертовски удобен, и может быть полезен читателям, о нем и мой рассказ.
Шаг 1. Берем статическую таблицу в БД (state), где хранятся разные параметры состояния сервера, например игровая дата, и добавляем поле server_type
Шаг 2. Делаем статически методы, которыми будем определять тип сервера
public static boolean isRoundServerType() { return serverType == SERVER_TYPE_ROUND || serverType == SERVER_TYPE_EVOLUTION; } public static boolean isEvolutionServerType() { return serverType == SERVER_TYPE_EVOLUTION; }
Шаг 3. Готовим методы для веб-интерфейса, чтобы определять тип сервера:
public boolean getRoundServer() { return State.isRoundServerType(); } public boolean getEvolutionServer() { return State.isEvolutionServerType(); }
Шаг 4. Во все места бизнес-логики вставляем блоки типа:
public String[] getProcessTimes() { if (State.isEvolutionServerType()) { String[] times = { "09.00", "11.00", "13.00", "15.00", "17.00", "19.00", "21.00" }; return times; } else if (State.isRoundServerType()) { String[] times = { "08.00", "10.00", "12.00", "14.00", "16.00", "18.00", "20.00" }; return times; } else { String[] times = { "09.30", "11.30", "13.30", "15.30", "17.30", "19.30", "22.30" }; return times; } }
Шаг 5. Во все места веб-интерфейса вставляем проверку на доступность элемента интерфейса.
<item name="corporationtotal" caption="Влияние корпораций" href="/corporation/controltotal/" hide="game.evolutionServer"/> <item name="citytotal" caption="Влияние корпораций" href="/corporation/citycontrol/" show="game.evolutionServer"/>
Тут надо сделать поправку на то, что мы используем самописный фреймворк, который позволяет скрывать элементы по двум видам правил:show/hide — где непосредственно вычисляется значение какого поля, allow/deny — тогда у пользователя, который залогинен запрашиваются права на какие-то действия. Последнее правило дает возможность вести разработку прямо в основной ветке репозитория, просто скрывая новый функционал правами например:
<button icon="adddoc" caption="Добавить новый город" action="javascript:dialog('/intercitynew/')" allow="admin:gamemanager"/>
Развивая тему дальше можно делать аналогичным образом сплит тестирование механик в разных частях игрового мира просто подменяя имплементацию старой логики на новую.
if (RetailFormula.isEvolutionRetail(currentCity.getId().toInteger())) { house = new House4(row, date); } else { house = new House2(row, date); }
Своего рода dependency injection, однако есть неоспоримые преимущества за счет гибкого и прозрачного варианта настроек алгоритма.
Код получается немного пухленький, зато имеет минимальные затраты на поддержку всей системы, т.к. фактически 1 флажок в БД менять логику работы сервера. Опыт показал, что особых проблем такое сожительство версий не вызывает, а перспективные разработки — заметно ускоряются.
