Middleware паттерн

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

Каждый middleware представляет собой функцию с единым интерфейсом:

  • принимает входные данные
  • выполняет операцию над ними
  • передаёт результат дальше или прерывает выполнение при ошибке

Типовая сигнатура:

function middleware(ctx, next) {
    // работа с ctx
    return next();
}

ctx — объект контекста, содержащий данные для валидации next — функция перехода к следующему этапу цепочки

Валидация с Validator.js часто строится поверх таких цепочек, где каждый middleware вызывает одну или несколько функций проверки из библиотеки.


Роль Validator.js в middleware-архитектуре

Validator.js предоставляет набор функций для проверки строковых значений: email, URL, длина, числовые диапазоны и другие. Сам по себе он не реализует middleware-подход, но хорошо в него встраивается.

Пример использования:

import validator from 'validator';

function isEmailMiddleware(ctx, next) {
    if (!validator.isEmail(ctx.value)) {
        ctx.errors.push('Некорректный email');
        return;
    }
    return next();
}

Здесь Validator.js выполняет атомарную проверку, а middleware управляет потоком выполнения.


Контекст выполнения как центральная структура

Для организации цепочки используется единый объект контекста:

const ctx = {
    value: 'test@example.com',
    errors: [],
    sanitized: null
};

Middleware работают с этим объектом, не создавая побочных эффектов вне цепочки.

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

  • единая точка хранения данных
  • возможность накопления ошибок
  • простая отладка

Построение цепочки middleware

Цепочка формируется как массив функций:

const middlewares = [
    normalizeInput,
    validateNotEmpty,
    isEmailMiddleware,
    sanitizeEmail
];

Исполнитель:

function runMiddlewares(ctx, middlewares) {
    let index = 0;

    function next() {
        const middleware = middlewares[index++];
        if (!middleware) return;
        return middleware(ctx, next);
    }

    return next();
}

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


Типы middleware в системе валидации

Преобразующие middleware

Отвечают за нормализацию данных до проверки:

function trimMiddleware(ctx, next) {
    if (typeof ctx.value === 'string') {
        ctx.value = ctx.value.trim();
    }
    return next();
}

Используются до любых проверок Validator.js.


Проверяющие middleware

Основной слой логики:

function lengthMiddleware(ctx, next) {
    if (!validator.isLength(ctx.value, { min: 5 })) {
        ctx.errors.push('Слишком короткое значение');
        return;
    }
    return next();
}

Каждый такой middleware отвечает за одну конкретную проверку.


Блокирующие middleware

Прерывают цепочку при критических ошибках:

function requiredMiddleware(ctx, next) {
    if (validator.isEmpty(ctx.value || '')) {
        ctx.errors.push('Поле обязательно');
        return;
    }
    return next();
}

Асинхронные middleware

Используются при необходимости внешних проверок:

async function uniqueEmailMiddleware(ctx, next) {
    const exists = await fakeDbCheck(ctx.value);
    if (exists) {
        ctx.errors.push('Email уже используется');
        return;
    }
    return next();
}

Middleware-движок должен поддерживать Promise:

async function runMiddlewaresAsync(ctx, middlewares) {
    let index = 0;

    async function next() {
        const middleware = middlewares[index++];
        if (!middleware) return;
        return await middleware(ctx, next);
    }

    return next();
}

Композиция правил валидации

Middleware позволяют собирать сложные правила из простых блоков:

const emailValidationPipeline = [
    trimMiddleware,
    requiredMiddleware,
    isEmailMiddleware,
    normalizeEmailMiddleware,
    uniqueEmailMiddleware
];

Такой подход заменяет монолитные функции валидации.


Обработка ошибок внутри цепочки

Ошибки накапливаются в контексте:

function hasErrors(ctx) {
    return ctx.errors.length > 0;
}

Middleware могут учитывать это:

function stopIfErrorsMiddleware(ctx, next) {
    if (hasErrors(ctx)) return;
    return next();
}

Это позволяет строить условные ветвления без усложнения архитектуры.


Расширяемость через плагины

Middleware-подход естественно поддерживает модульность. Новые проверки легко добавляются без изменения существующего кода:

function isNumericMiddleware(ctx, next) {
    if (!validator.isNumeric(ctx.value)) {
        ctx.errors.push('Значение должно быть числом');
        return;
    }
    return next();
}

Добавление:

middlewares.push(isNumericMiddleware);

Повторное использование логики

Один и тот же middleware можно применять к разным схемам валидации:

const passwordPipeline = [
    trimMiddleware,
    requiredMiddleware,
    lengthMiddleware
];

const agePipeline = [
    requiredMiddleware,
    isNumericMiddleware
];

Это снижает дублирование кода и упрощает сопровождение.


Управление порядком выполнения

Порядок middleware критичен. Например:

  1. нормализация
  2. обязательность
  3. формат
  4. внешние проверки

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


Интеграция с объектными схемами

Middleware легко масштабируются до работы с объектами:

function fieldMiddleware(field, pipeline) {
    return (ctx, next) => {
        const fieldCtx = {
            value: ctx.data[field],
            errors: []
        };

        return runMiddlewares(fieldCtx, pipeline).then(() => {
            ctx.errors[field] = fieldCtx.errors;
            return next();
        });
    };
}

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

Middleware позволяет оптимизировать выполнение:

  • ранний выход при ошибках
  • пропуск тяжёлых проверок
  • кэширование результатов

Пример:

function skipIfInvalidEmail(ctx, next) {
    if (ctx.errors.includes('email')) return;
    return next();
}

Ограничения подхода

Middleware-архитектура усложняет:

  • отслеживание полного потока данных
  • отладку глубоко вложенных цепочек
  • управление асинхронностью без строгого стандарта

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


Сочетание с Validator.js в крупных системах

В масштабных приложениях Validator.js используется только как низкоуровневый слой. Middleware берут на себя:

  • оркестрацию проверок
  • порядок выполнения
  • обработку ошибок
  • интеграцию с бизнес-логикой

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