Валидация перед миграцией

При изменении структуры данных в приложениях, использующих JSON-документы, ключевую роль играет контроль совместимости старых и новых форматов. Любая миграция — это переход между двумя состояниями схемы, где некорректные данные способны нарушить целостность системы, привести к частичной потере информации или ошибкам исполнения бизнес-логики.

В экосистеме JavaScript для формализованной валидации JSON-структур широко применяется библиотека Ajv (Another JSON Schema Validator). Она обеспечивает строгую проверку соответствия данных JSON Schema и позволяет встроить этап валидации непосредственно в процесс миграции.


Формализация схемы как основа миграционной безопасности

JSON Schema выступает контрактом между версиями данных. Перед миграцией фиксируется две схемы:

  • исходная схема (текущая структура данных)
  • целeвая схема (структура после миграции)

Ajv компилирует эти схемы в оптимизированные функции проверки, что позволяет выполнять массовую валидацию без существенных накладных расходов.

Пример базовой схемы пользователя:

const schemaV1 = {
  type: "object",
  properties: {
    id: { type: "string" },
    name: { type: "string" }
  },
  required: ["id", "name"],
  additionalProperties: false
};

Целевая версия схемы:

const schemaV2 = {
  type: "object",
  properties: {
    id: { type: "string" },
    fullName: { type: "string" },
    createdAt: { type: "string", format: "date-time" }
  },
  required: ["id", "fullName"],
  additionalProperties: false
};

Подготовка Ajv для миграционных сценариев

Ajv предоставляет режимы, позволяющие повысить строгость проверки и предотвратить неявные ошибки при трансформации данных.

import Ajv from "ajv";

const ajv = new Ajv({
  allErrors: true,
  strict: true,
  removeAdditional: false
});

Ключевые параметры:

  • allErrors — сбор всех ошибок, а не остановка на первой
  • strict — включение строгого режима проверки схем
  • removeAdditional — контроль лишних полей, критично при миграциях

Проверка данных перед миграцией

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

const validateV1 = ajv.compile(schemaV1);

function validateBeforeMigration(dataset) {
  const invalidRecords = [];

  for (const record of dataset) {
    const valid = validateV1(record);
    if (!valid) {
      invalidRecords.push({
        record,
        errors: validateV1.errors
      });
    }
  }

  return invalidRecords;
}

Такой этап формирует контрольную точку качества данных до любых преобразований.


Валидация после преобразования структуры

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

const validateV2 = ajv.compile(schemaV2);

function validateAfterMigration(transformedDataset) {
  return transformedDataset.filter(record => {
    return validateV2(record);
  });
}

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


Сценарии несовместимости схем

Миграции часто сопровождаются изменением семантики данных. Ajv позволяет выявлять следующие типы конфликтов:

  • удаление обязательных полей
  • изменение типов данных (string → number)
  • перенос значений в новые структуры
  • добавление новых обязательных полей без дефолтов
  • изменение форматов (например, даты)

Пример проблемной трансформации:

// было
{ id: "1", name: "Alex" }

// стало
{ id: "1", fullName: "Alex", createdAt: "2024-01-01T00:00:00Z" }

Если поле createdAt становится обязательным, Ajv выявит несовместимость до завершения миграции.


Использование форматов и кастомных проверок

Ajv поддерживает строгую проверку форматов, что особенно важно при миграциях временных данных.

const schema = {
  type: "object",
  properties: {
    createdAt: { type: "string", format: "date-time" }
  },
  required: ["createdAt"]
};

Для расширенной логики применяются пользовательские форматы:

ajv.addFormat("unix-timestamp", {
  type: "number",
  validate: value => Number.isInteger(value) && value > 0
});

Предобработка данных через нормализацию

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

function normalize(record) {
  return {
    ...record,
    fullName: record.fullName || record.name,
    createdAt: record.createdAt || new Date().toISOString()
  };
}

Далее нормализованные данные проходят через Ajv как через финальный фильтр соответствия схемам.


Проверка обратной совместимости

В миграционных сценариях важно контролировать, что новая схема не нарушает поддержку старых данных.

const validateBackward = ajv.compile(schemaV2);

function checkBackwardCompatibility(oldDataset) {
  return oldDataset.every(record => validateBackward(record));
}

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


Массовая валидация больших наборов данных

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

Оптимизированный поток:

function migrate(dataset) {
  const valid = [];
  const invalid = [];

  for (let i = 0; i < dataset.length; i++) {
    const normalized = normalize(dataset[i]);

    if (validateV2(normalized)) {
      valid.push(normalized);
    } else {
      invalid.push(validateV2.errors);
    }
  }

  return { valid, invalid };
}

Использование строгого режима при миграциях

Strict mode в Ajv позволяет выявлять потенциальные проблемы схем ещё до выполнения валидации.

Ключевые аспекты строгой проверки:

  • обнаружение неиспользуемых ключей
  • предупреждения о некорректных типах схем
  • контроль конфликтов keywords
  • проверка структуры JSON Schema
const ajv = new Ajv({
  strict: true,
  strictTypes: true,
  strictTuples: true
});

Интеграция в миграционный пайплайн

В типовой архитектуре миграции Ajv используется на нескольких этапах:

  1. проверка исходных данных
  2. контроль трансформации
  3. валидация новой структуры
  4. проверка обратной совместимости
  5. фильтрация неконсистентных записей

Такой подход формирует защитный слой между версиями схем, минимизируя риск повреждения данных при эволюции модели хранения.