Мы используем 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 флажок в БД менять логику работы сервера. Опыт показал, что особых проблем такое сожительство версий не вызывает, а перспективные разработки — заметно ускоряются.
Комментарии (5)
sergey1borisov
29.01.2017 16:17В Вашей ситуации посоветовал бы использовать шаблон "стратегия", и делать проверку на тип сервера только в одном месте — во время инициализации стратегии с логикой работы приложения, зависящей от типа сервера. Кратко, лаконично и никаких условных проверок на всех просторах исходников приложения.
qdreadknight
29.01.2017 19:06Мой проект состоит из такого количества классов, что шаблон «стратегия» мне кажется здесь абсолютно неуместным на глобальном уровне. Применить его конечно можно, но тогда сразу вешаться. Концов не найдешь потом. А вот варианты алгоритмов спроса реализуют разные классы, так что локально это работает.
elegorod
02.02.2017 15:38Когда-то делали похожим образом, но тут есть проблемы:
1. При рефакторинге большой шанс сломать старый код. Например, переделываете вы механику осад, в объекте осад были поля String startTime, String endTime, Date date. Переходите на Джаву 8, решили сделать через правильные Instant: Instant start, Instant end. Старый код работает со старыми полями (ожидая, что они not null), новый — с новыми. Большая вероятность, что тут что-то сломается, особенно во время первода часов.
2. Трудно делать миграции базы данных. Тот же пример с осадами. Старый код вставляет в базу 3 старых поля, новый — 2 новых. При этом падает с ошибками, что не указаны значения для not null полей.
3. Не всё можно спрятать за правами доступа. Особенно, если это переделка уже работающих механик.
ApeCoder
Ура, вы изобрели feature toggle!
qdreadknight
Я догадывался, что не открыл америку. А оказывается у этого есть задокументированое название.
Статья в целом про то, что это работает и это удобно в продакшене. Давным давно мы пробовали ветки в репозитории, но это был какой-то кромешный ад по факту.