Разделение ответственности в архитектуре валидации данных в Vest строится вокруг идеи изоляции бизнес-правил от пользовательского интерфейса, состояния компонентов и инфраструктурного кода. Основной принцип заключается в том, что логика проверки данных существует как самостоятельная сущность, не зависящая от способа ввода, отображения ошибок или фреймворка, в котором используется результат.
В классических подходах валидация часто «расползается» по приложению: часть правил оказывается в компонентах интерфейса, часть — в API-контроллерах, часть — в утилитах. Это приводит к дублированию логики и усложнению поддержки.
Vest предлагает иной подход: валидация оформляется как независимая декларативная система, представляющая собой набор тестов (suites), которые выполняются над входными данными.
Ключевой объект — suite, внутри которого описываются правила проверки:
import { create, test, enforce } from 'vest';
const loginSuite = create((data = {}) => {
test('email', 'Некорректный email', () => {
enforce(data.email).isEmail();
});
test('password', 'Пароль слишком короткий', () => {
enforce(data.password).longerThan(5);
});
});
В этой модели отсутствует зависимость от UI: suite ничего не знает о том, где будут показаны ошибки и как именно пользователь взаимодействует с формой.
Разделение ответственности в Vest особенно заметно на уровне доменной логики. Правила валидации описываются как чистые функции, зависящие только от входных данных.
Это позволяет:
Пример использования одной suite в разных контекстах:
const result = loginSuite({ email: 'test@mail.com', password: '123456' });
if (result.hasErrors()) {
console.log(result.getErrors());
}
Тот же набор правил может применяться в API-слое без модификации, что демонстрирует строгое разделение ответственности между проверкой данных и их обработкой.
Vest использует терминологию тестов для описания валидации. Каждый
test — это атомарная единица проверки, что усиливает
декомпозицию логики.
test('username', 'Имя пользователя обязательно', () => {
enforce(data.username).isNotEmpty();
});
Каждый тест отвечает только за одну конкретную проверку. Это ограничение играет ключевую роль в архитектуре:
Такой подход формирует структуру, в которой validation suite становится аналогом набора unit-тестов, выполняемых не над кодом, а над входными данными.
В Vest можно выделить три независимых слоя:
1. Данные (Input Layer) Это входной объект, содержащий значения, подлежащие проверке. Он не модифицируется внутри suite.
2. Правила (Validation Layer) Набор деклараций через
test, enforce, группировки и условия
выполнения.
3. Результат (Result Layer) Объект, содержащий ошибки, статус валидности и вспомогательную информацию.
const result = loginSuite(data);
result.hasErrors();
result.getErrors('email');
result.getAllErrors();
Разделение этих уровней обеспечивает отсутствие побочных эффектов: suite не изменяет данные и не зависит от состояния приложения.
Одним из ключевых аспектов разделения ответственности является отсутствие привязки к интерфейсу. Vest не предполагает, каким образом будут отображаться ошибки.
Это позволяет интегрировать одну и ту же валидационную логику в различные UI-архитектуры:
Пример использования в UI-логике:
const result = loginSuite(formState);
setErrors(result.getErrors());
Валидация при этом остаётся полностью независимой и может существовать вне жизненного цикла компонента.
Vest вводит механизм групп (group), который позволяет
логически разделять наборы проверок внутри одной suite. Это особенно
важно для сложных форм и сценариев с условной логикой.
import { group } from 'vest';
group('auth', () => {
test('email', 'invalid email', () => {
enforce(data.email).isEmail();
});
test('password', 'invalid password', () => {
enforce(data.password).longerThan(8);
});
});
Группы позволяют:
Таким образом, одна suite может содержать несколько независимых поддоменов логики.
Разделение ответственности усиливается за счёт механизмов условного
выполнения: skip, only, динамические
проверки.
test('companyName', 'required', () => {
if (!data.isCompany) return;
enforce(data.companyName).isNotEmpty();
});
Или через встроенные механизмы управления:
import { skip } from 'vest';
if (!data.email) {
skip('email');
}
Такие инструменты позволяют разделять:
В результате validation suite не превращается в монолитный блок условий, а сохраняет структурированность.
Отдельное направление разделения ответственности связано с асинхронными проверками, такими как запросы к серверу.
test('email', 'email already taken', async () => {
const exists = await api.checkEmail(data.email);
enforce(exists).isFalsy();
});
Асинхронность не смешивается с синхронной логикой: каждый тест остаётся независимым, а выполнение управляется движком Vest.
Это разделяет:
Такое разделение критично для масштабируемых приложений, где валидация становится частью распределённой системы.
Чёткое разделение ответственности приводит к тому, что validation suite становится переиспользуемым модулем. Он может быть:
export const emailRules = (email) => {
test('email', () => {
enforce(email).isEmail();
});
};
Эти правила затем интегрируются в разные suites, не нарушая изоляцию логики.
Vest поддерживает композиционный подход, при котором suites могут комбинироваться.
const userSuite = create((data) => {
loginSuite(data);
profileSuite(data);
});
Композиция позволяет:
Каждый модуль остаётся автономным, но может быть включён в более крупную структуру.
Важный аспект разделения ответственности — отсутствие скрытых зависимостей между проверками. Каждая validation suite получает данные явно и не использует глобальное состояние.
const suite = create((data) => {
test('field', () => {
enforce(data.field).isNotEmpty();
});
});
Это исключает:
Подход Vest коррелирует с принципами разделения слоёв в чистой архитектуре:
Validation suite в этой модели становится аналогом доменного сервиса, не привязанного к конкретной реализации приложения.
При росте приложения сложность валидации обычно увеличивается экспоненциально. Vest решает эту проблему через структурное разделение:
Такая организация позволяет удерживать сложные системы в предсказуемых границах, где каждая часть отвечает за строго ограниченную область поведения.