Обратная совместимость

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

В экосистеме JavaScript библиотека Validator.js используется во множестве проектов:

  • backend-приложения на Node.js;
  • REST API;
  • GraphQL-серверы;
  • frontend-формы;
  • middleware Express;
  • ORM и ODM-решения;
  • системы аутентификации.

Даже небольшое изменение логики проверки строк может затронуть тысячи строк кода.


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

Validator.js придерживается принципов SemVer:

MAJOR.MINOR.PATCH

Пример:

13.11.0

Расшифровка:

Компонент Значение
MAJOR Ломающие изменения
MINOR Новый функционал без поломки API
PATCH Исправления ошибок

Ломающие изменения

Ломающими считаются изменения, при которых старый код начинает работать иначе или перестаёт работать вообще.

Примеры:

  • изменение сигнатуры функции;
  • удаление метода;
  • изменение значения по умолчанию;
  • изменение логики проверки;
  • изменение формата возвращаемых данных.

Пример изменения поведения валидатора

Старое поведение

const validator = require('validator');

validator.isEmail('test@test');

В старых версиях некоторые некорректные email могли проходить проверку.


Новое поведение

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

validator.isEmail('test@test');
// false

Такое изменение повышает корректность валидации, но способно сломать старые формы регистрации.


Поддержка старого API

Validator.js старается сохранять существующие методы даже после появления новых механизмов.

Например:

validator.isURL(url);

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


Deprecated API

Иногда API помечается как устаревший.

Признаки deprecated-функциональности:

  • предупреждение в changelog;
  • предупреждение в документации;
  • сохранение работы функции;
  • рекомендация перейти на новый API.

Стратегия миграции

Типичная схема поддержки совместимости:

  1. Добавление нового API.
  2. Сохранение старого API.
  3. Пометка старого API как deprecated.
  4. Удаление в следующем major-релизе.

Пример миграции параметров

Старый вариант

validator.isLength(str, 5, 20);

Новый вариант

validator.isLength(str, {
    min: 5,
    max: 20
});

Объект параметров:

  • легче расширять;
  • проще читать;
  • безопаснее для совместимости;
  • позволяет добавлять новые настройки без изменения сигнатуры.

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

Изменение порядка аргументов считается ломающим изменением.

Проблемный пример

Версия 1

validator.contains(str, seed);

Версия 2

validator.contains(seed, str);

Старый код начнёт работать неправильно без ошибок времени выполнения.


Опасность скрытых изменений

Некоторые изменения выглядят безопасно, но нарушают совместимость.

Изменение значения по умолчанию

Старое поведение

validator.isURL(url);

Допускались URL без протокола.


Новое поведение

Теперь требуется http:// или https://.

validator.isURL('example.com');
// false

Защита через явные настройки

Чтобы уменьшить влияние обновлений, рекомендуется явно задавать параметры.

Нестабильный вариант

validator.isURL(url);

Стабильный вариант

validator.isURL(url, {
    require_protocol: false
});

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


Совместимость в middleware

Validator.js часто используется внутри middleware.

Пример

app.post('/register', (req, res) => {
    if (!validator.isEmail(req.body.email)) {
        return res.status(400).send('Invalid email');
    }

    res.send('OK');
});

После обновления библиотеки часть email может перестать проходить проверку.


Регрессионное тестирование

Главный механизм контроля совместимости — автоматические тесты.


Проверка поведения

describe('Email validation', () => {
    test('must validate old emails', () => {
        expect(
            validator.isEmail('admin@example.com')
        ).toBe(true);
    });
});

Snapshot-тестирование

Иногда проверяется не только результат, но и структура ошибок.

expect(result).toMatchSnapshot();

После обновления Validator.js snapshot позволяет обнаружить изменение поведения.


Совместимость sanitize-функций

Validator.js содержит функции очистки данных.

Пример

validator.escape('<script>');

Результат:

&lt;script&gt;

Изменение алгоритма экранирования способно повлиять:

  • на HTML;
  • на шаблоны;
  • на markdown;
  • на рендеринг frontend-компонентов.

Изменение Unicode-логики

Поддержка Unicode регулярно развивается.


Старое поведение

Некоторые символы не распознавались:

validator.isAlpha('Привет');

Новое поведение

После расширения локалей:

validator.isAlpha('Привет', 'ru-RU');
// true

Это улучшение функциональности, но иногда оно меняет старую бизнес-логику.


Совместимость локалей

Validator.js поддерживает множество локалей:

validator.isAlpha(str, 'de-DE');
validator.isAlpha(str, 'fr-FR');
validator.isAlpha(str, 'ru-RU');

Добавление новых локалей обычно безопасно, но изменение существующих правил — потенциально ломающая операция.


Изменения регулярных выражений

Многие валидаторы основаны на regex.


Пример

validator.isMobilePhone(phone, 'ru-RU');

После обновления regex:

  • некоторые номера могут стать валидными;
  • старые номера могут перестать проходить проверку.

Совместимость Node.js

Версии Validator.js зависят от поддерживаемых версий Node.js.


Пример

Старая версия:

Node.js >= 10

Новая версия:

Node.js >= 18

Это типичный major-breaking change.


Проверка engine-политики

В package.json:

{
  "engines": {
    "node": ">=18"
  }
}

Роль changelog

Changelog — основной источник информации о совместимости.

Типичные разделы:

  • Breaking Changes
  • Deprecated
  • Fixed
  • Added
  • Removed

Анализ changelog перед обновлением

Нельзя обновлять Validator.js вслепую.


Опасный вариант

npm update

Контролируемый вариант

npm install validator@13.11.0

Фиксация версии

Для стабильности проекта часто фиксируется точная версия.

Строгая фиксация

{
  "dependencies": {
    "validator": "13.11.0"
  }
}

Плавающая версия

{
  "dependencies": {
    "validator": "^13.11.0"
  }
}

Символ ^ допускает автоматические minor-обновления.


Риски автоматических обновлений

Даже minor-релизы иногда меняют поведение:

  • исправляют regex;
  • обновляют Unicode;
  • делают проверки строже;
  • изменяют edge-case логику.

Защита через lock-файлы

npm:

package-lock.json

Yarn:

yarn.lock

pnpm:

pnpm-lock.yaml

Lock-файлы фиксируют точные версии зависимостей.


Peer dependency и совместимость

Некоторые библиотеки используют Validator.js как peer dependency.

Пример:

{
  "peerDependencies": {
    "validator": "^13.0.0"
  }
}

Нарушение диапазона версий может привести к конфликтам.


Совместимость с Express Validator

Библиотека Express Validator тесно связана с Validator.js.


Пример

body('email').isEmail()

Внутри используется Validator.js.

Изменение логики Validator.js автоматически влияет на middleware Express Validator.


Адаптеры совместимости

Иногда создаются промежуточные обёртки.

Пример

function validateEmail(email) {
    return validator.isEmail(email, {
        allow_utf8_local_part: false
    });
}

Преимущества:

  • централизованная настройка;
  • контроль поведения;
  • упрощение миграции;
  • единая точка изменений.

Версионирование собственных обёрток

Крупные проекты редко используют Validator.js напрямую.

Создаётся внутренний слой:

export class ValidationService {
    static isEmail(email) {
        return validator.isEmail(email);
    }
}

Такой подход изолирует бизнес-логику от внешней библиотеки.


Контрактное тестирование

Контрактные тесты фиксируют ожидаемое поведение.

Пример

const validEmails = [
    'admin@test.com',
    'user@example.org'
];

for (const email of validEmails) {
    expect(validator.isEmail(email)).toBe(true);
}

Проверка edge-case сценариев

Особое внимание уделяется нестандартным данным.

Примеры

validator.isEmail('');
validator.isEmail(null);
validator.isEmail(undefined);
validator.isEmail('a@a');
validator.isEmail('@@@');

Именно edge-case случаи чаще всего меняются между версиями.


Поддержка CommonJS и ESM

Validator.js используется как в CommonJS, так и в ES Modules.


CommonJS

const validator = require('validator');

ESM

import validator from 'validator';

Изменение схемы экспорта — потенциально критическое breaking change.


Dual package hazard

Проблема возникает при одновременной поддержке:

  • CommonJS;
  • ESM.

Разные сборщики могут интерпретировать библиотеку по-разному.


Совместимость TypeScript

Validator.js содержит TypeScript-типизацию.


Пример

validator.isEmail(email);

Изменение типов способно ломать проект даже без изменения runtime-поведения.


Breaking changes в типах

Старая версия

isEmail(str: string): boolean;

Новая версия

isEmail(str: unknown): boolean;

Либо наоборот — более строгие ограничения.


Роль CI/CD

Системы CI автоматически проверяют совместимость.


Пример pipeline

steps:
  - npm install
  - npm test

После обновления Validator.js тесты сразу обнаружат проблемы.


Canary-обновления

Крупные системы обновляют зависимости постепенно.

Схема:

  1. обновление на тестовом окружении;
  2. проверка логов;
  3. анализ ошибок;
  4. rollout в production.

Feature flags

Иногда новая логика валидации включается флагами.

if (features.strictEmailValidation) {
    return validator.isEmail(email);
}

Совместимость API-контрактов

Изменение правил валидации влияет на API.


Пример

Старый API принимал:

{
  "phone": "12345"
}

После обновления Validator.js запрос начинает отклоняться.

Это может нарушить работу мобильных приложений и сторонних интеграций.


Обратная совместимость и безопасность

Иногда несовместимость вводится намеренно ради безопасности.


Пример

Более строгая проверка URL:

validator.isURL(url, {
    protocols: ['https']
});

Старые небезопасные URL могут перестать проходить валидацию.


Миграционные стратегии

Постепенная миграция

if (useNewValidation) {
    return newValidator(data);
}

return oldValidator(data);

Параллельная проверка

const oldResult = oldValidator(data);
const newResult = newValidator(data);

logDifference(oldResult, newResult);

Такой подход позволяет оценить влияние обновления до полного перехода.


Обратная совместимость пользовательских валидаторов

Часто поверх Validator.js создаются собственные проверки.


Пример

function isCorporateEmail(email) {
    return validator.isEmail(email)
        && email.endsWith('@company.com');
}

Изменение базового валидатора влияет и на пользовательскую логику.


Тестирование после обновления

Минимальный набор проверок:

  • регистрация;
  • авторизация;
  • формы;
  • API;
  • импорт данных;
  • edge-case значения;
  • Unicode;
  • локали;
  • мобильные номера;
  • URL;
  • HTML escaping.

Подходы к безопасному обновлению

Изоляция зависимости

import ValidationService from './services/validation.js';

Централизация настроек

export const emailOptions = {
    allow_utf8_local_part: false,
    require_tld: true
};

Полное покрытие тестами

npm test

Контроль changelog

Перед каждым major-обновлением анализируются:

  • удалённые API;
  • deprecated-функции;
  • изменения regex;
  • изменения Unicode;
  • изменения default options;
  • изменения Node.js compatibility.

Пример безопасного обновления

Шаг 1

Текущая версия:

{
  "validator": "13.7.0"
}

Шаг 2

Изучение changelog версии 13.11.0.


Шаг 3

Обновление:

npm install validator@13.11.0

Шаг 4

Запуск тестов:

npm test

Шаг 5

Проверка production-логов.


Типичные проблемы совместимости

Проблема Последствие
Изменение regex Неверная валидация
Новый Unicode Изменение допустимых символов
Строгие URL-проверки Ошибки API
Изменение типов TS Ошибки компиляции
Удаление deprecated API Runtime errors
Новый Node.js requirement Невозможность запуска

Принципы устойчивой архитектуры

Надёжная интеграция Validator.js строится на нескольких принципах:

  • изоляция библиотеки;
  • фиксированные версии;
  • автоматические тесты;
  • централизованные настройки;
  • контроль changelog;
  • постепенная миграция;
  • контрактное тестирование;
  • проверка edge-case сценариев;
  • CI/CD-проверки;
  • отказ от прямого использования Validator.js во всех слоях приложения.