Три ветки продукта и контроль версий

в 14:51, , рубрики: java, Анализ и проектирование систем

В 2015 году, разрабатывая браузерную онлайн-игру, встала следующая задача: сделать второй сервер с другими настройками и механиками игры, а также вести перспективную разработку новой версии игры, в которой будет еще больше изменений, при этом общий каркас у всех версий игры одинаковый. Дело осложняется тем, что игре требуются постоянные обновление и исправления, т.к. игровой процесс идет и останавливать его из-за разработки параллельной версии никак нельзя.

Мы используем 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 флажок в БД менять логику работы сервера. Опыт показал, что особых проблем такое сожительство версий не вызывает, а перспективные разработки — заметно ускоряются.

Автор: qdreadknight

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js