Версионирование конфигурации

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

Конфигурация Kepler.gl представляет собой сериализуемый JSON-объект, включающий состояние визуализации (visState), параметры карты (mapState) и наборы данных (datasets). Именно эта структура определяет поведение карты и является основным кандидатом для версионирования.

Ключевые области конфигурации:

  • visState — слои, фильтры, взаимодействия, стили визуализации
  • mapState — центр карты, масштаб, поворот, проекция
  • datasets — загруженные данные и их метаданные

Любое изменение структуры этих блоков между версиями приложения может привести к несовместимости сохранённых конфигураций.

Базовые стратегии версионирования

Явное поле версии конфигурации

Наиболее распространённый подход — добавление поля версии в корень конфигурации:

{
  "version": "1.0.0",
  "config": {
    "visState": { },
    "mapState": { },
    "datasets": []
  }
}

Поле версии позволяет однозначно определить, какие правила миграции необходимо применить при загрузке конфигурации.

Семантическое версионирование

Использование схемы SemVer (MAJOR.MINOR.PATCH) особенно эффективно:

  • MAJOR — несовместимые изменения структуры конфигурации
  • MINOR — добавление новых возможностей без нарушения обратной совместимости
  • PATCH — исправления и безопасные изменения

Такой подход облегчает поддержку миграций при обновлении интерфейса визуализации.

Версионирование через хеш состояния

Альтернативный метод — вычисление хеша от нормализованного JSON:

import stableStringify from 'fast-json-stable-stringify';
import crypto from 'crypto';

function getConfigHash(config) {
  const normalized = stableStringify(config);
  return crypto.createHash('sha256').update(normalized).digest('hex');
}

Этот подход позволяет отслеживать любые изменения состояния, включая неявные модификации порядка полей.

Миграции конфигураций

Принцип цепочки миграций

Каждая версия конфигурации должна иметь функцию преобразования в следующую:

const migrations = {
  "1.0.0": (config) => {
    return {
      ...config,
      visState: {
        ...config.visState,
        filters: config.visState.filters || []
      }
    };
  },
  "1.1.0": (config) => {
    return {
      ...config,
      mapState: {
        ...config.mapState,
        pitch: config.mapState.pitch ?? 0
      }
    };
  }
};

Миграции применяются последовательно до достижения актуальной версии.

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

function migrateConfig(config, targetVersion) {
  let current = config;
  let version = config.version;

  while (version !== targetVersion) {
    const migrate = migrations[version];
    if (!migrate) break;

    current = migrate(current);
    version = getNextVersion(version);
    current.version = version;
  }

  return current;
}

Хранение конфигурации

LocalStorage как базовый уровень

Простейший вариант хранения:

function saveConfig(config) {
  localStorage.setItem('kepler_config', JSON.stringify(config));
}

function loadConfig() {
  const raw = localStorage.getItem('kepler_config');
  return raw ? JSON.parse(raw) : null;
}

Недостаток — отсутствие централизованного управления версиями и миграциями.

Серверное хранение конфигураций

При использовании backend-архитектуры конфигурации часто сохраняются в базе данных как JSONB (PostgreSQL) или документ (MongoDB).

Структура записи:

  • user_id
  • config_version
  • config_payload
  • created_at
  • updated_at

Это позволяет реализовать откат и историю изменений.

Совместимость слоёв и фильтров

Версионирование слоёв

Слои Kepler.gl имеют собственную внутреннюю структуру, которая может меняться:

  • добавление новых типов визуализаций
  • изменение атрибутов данных
  • обновление стилей рендеринга

Пример обработки несовместимого слоя:

function normalizeLayer(layer) {
  if (!layer.config) {
    layer.config = {};
  }

  return {
    ...layer,
    config: {
      opacity: layer.config.opacity ?? 1,
      thickness: layer.config.thickness ?? 2
    }
  };
}

Фильтры как источник нестабильности

Фильтры часто являются наиболее изменяемой частью состояния:

  • изменение типов фильтров
  • добавление новых операторов
  • изменение формата диапазонов

Поэтому фильтры требуют отдельной стратегии миграции:

function migrateFilter(filter) {
  switch (filter.type) {
    case 'range':
      return {
        ...filter,
        value: Array.isArray(filter.value)
          ? filter.value
          : [filter.value.min, filter.value.max]
      };
    default:
      return filter;
  }
}

Изоляция версий визуализации

Версионирование схемы данных

Отдельно от конфигурации визуализации необходимо версионировать структуру входных данных:

  • названия колонок
  • типы данных
  • вычисляемые поля

Пример метаданных:

{
  "datasetVersion": "2.3.0",
  "fields": [
    { "name": "lat", "type": "float" },
    { "name": "lng", "type": "float" }
  ]
}

При изменении схемы данных конфигурация визуализации может стать некорректной без соответствующей миграции.

Совместимость с обновлениями Kepler.gl

Изменения внутри Kepler.gl могут включать:

  • изменение структуры visState
  • добавление новых типов слоёв
  • переработку механизма взаимодействия
  • изменение формата экспорта конфигураций

Поэтому конфигурация должна быть защищена слоем адаптации:

function adaptKeplerConfig(config) {
  return {
    ...config,
    visState: adaptVisState(config.visState),
    mapState: adaptMapState(config.mapState)
  };
}

Детерминированность конфигурации

Для корректного версионирования важно обеспечить детерминированный порядок:

  • сортировка слоёв
  • фиксированный порядок фильтров
  • нормализация координатных значений
  • унификация типов данных

Это позволяет избежать ложных различий между версиями конфигураций.

function normalizeConfig(config) {
  return {
    ...config,
    visState: {
      ...config.visState,
      layers: [...config.visState.layers].sort((a, b) => a.id - b.id),
      filters: [...config.visState.filters].sort((a, b) => a.id - b.id)
    }
  };
}

Управление конфликтами версий

При загрузке устаревшей конфигурации возможны конфликты:

  • отсутствующие поля
  • переименованные параметры
  • удалённые типы слоёв
  • несовместимые фильтры

Стратегии обработки:

  • fallback значения — подстановка дефолтов
  • игнорирование неизвестных полей
  • частичная загрузка конфигурации
  • логирование деградации состояния

Эволюция схемы конфигурации

Развитие системы конфигураций требует строгого контроля изменений:

  • введение схемы JSON Schema для валидации
  • автоматические тесты миграций
  • контроль обратной совместимости
  • фиксация breaking changes на уровне MAJOR версии

Применение схемы валидации:

import Ajv from 'ajv';

const ajv = new Ajv();
const validate = ajv.compile(keplerConfigSchema);

function validateConfig(config) {
  const valid = validate(config);
  if (!valid) {
    throw new Error('Invalid configuration');
  }
}

Версионирование конфигурации в Kepler.gl становится не вспомогательной задачей, а центральным механизмом стабильности системы визуализации, определяющим возможность долгосрочного развития интерфейса, сохранения пользовательских сценариев и предсказуемого поведения картографических приложений.