Валидация JSON-схем в JavaScript через Ajv опирается на преобразование схемы в исполняемую функцию. Любая схема JSON Schema, переданная в валидатор, проходит стадию анализа и компиляции, после чего превращается в оптимизированный JavaScript-код. Именно этот этап является ключевым с точки зрения производительности, особенно в высоконагруженных системах.
Предварительная компиляция заключается в переносе этого процесса из рантайма в этап сборки приложения. Схемы преобразуются в готовые функции заранее, что позволяет исключить накладные расходы на генерацию кода при выполнении программы.
Внутренне Ajv выполняет несколько стадий обработки схемы:
$ref;Основной метод рантайм-компиляции:
const Ajv = require("ajv");
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
id: { type: "number" }
},
required: ["id"]
};
const validate = ajv.compile(schema);
console.log(validate({ id: 1 })); // true
Каждый вызов compile создаёт новую функцию. При
многократном использовании одинаковых схем это приводит к лишним
затратам CPU.
Ajv поддерживает кэширование уже скомпилированных схем внутри одного экземпляра:
const validate1 = ajv.compile(schema);
const validate2 = ajv.compile(schema);
validate1 === validate2; // true
При повторной компиляции идентичной схемы возвращается уже созданная
функция. Однако это работает только в рамках одного процесса и одного
экземпляра Ajv.
При масштабировании приложения или использовании серверлесс-архитектур этого недостаточно, поскольку инициализация валидаторов происходит заново.
Проблемы подхода “compile on demand”:
Эти ограничения особенно заметны при использовании большого количества схем или микросервисной архитектуры.
Ajv поддерживает подход, при котором функции валидаторов создаются заранее и сохраняются как обычный JavaScript-код. Это достигается через генерацию standalone-модулей.
Пример использования standalone:
const Ajv = require("ajv");
const standaloneCode = require("ajv/dist/standalone").default;
const ajv = new Ajv({ code: { source: true } });
const schema = {
$id: "UserSchema",
type: "object",
properties: {
name: { type: "string" }
},
required: ["name"]
};
const validate = ajv.compile(schema);
const moduleCode = standaloneCode(ajv, validate);
console.log(moduleCode);
Результатом будет строка JavaScript-кода, содержащая готовую функцию валидации без необходимости повторной компиляции.
Предварительная компиляция обычно переносится в этап сборки проекта.
Типичный процесс:
Пример генерации модуля:
const fs = require("fs");
const Ajv = require("ajv");
const standaloneCode = require("ajv/dist/standalone").default;
const ajv = new Ajv({ code: { source: true } });
const schema = require("./schemas/user.json");
const validate = ajv.compile(schema);
const code = standaloneCode(ajv, validate);
fs.writeFileSync("./validators/user.js", code);
После этого в приложении используется уже готовый модуль:
const validate = require("./validators/user");
validate({ name: "Alex" });
Предварительная компиляция влияет на несколько ключевых аспектов:
Особенно заметный эффект наблюдается при большом количестве схем или частом создании новых экземпляров приложения.
При использовании $id схемы могут быть связаны между
собой через $ref. Это влияет на стратегию предварительной
компиляции.
const schema = {
$id: "Address",
type: "object",
properties: {
city: { type: "string" }
}
};
const userSchema = {
$id: "User",
type: "object",
properties: {
address: { $ref: "Address" }
}
};
В случае предварительной компиляции важно, чтобы все зависимости были доступны в момент генерации кода, иначе разрешение ссылок невозможно.
Несмотря на преимущества, предварительно сгенерированный код имеет ограничения:
$id и
$ref.В проектах с Webpack или Vite генерация валидаторов часто переносится в отдельный build-скрипт. Это позволяет исключить Ajv из клиентского бандла или уменьшить его роль до минимального runtime.
Типовая схема:
Применяются несколько моделей:
Выбор зависит от архитектуры приложения и требований к времени отклика.
Использование предварительной компиляции часто приводит к изменению структуры проекта:
Такая модель особенно характерна для больших сервисов с жёстко определёнными контрактами данных.