Роль валидации в
многослойной архитектуре
Валидация данных существует на разных уровнях системы, и каждый
уровень решает собственный класс задач. Ошибкой проектирования
становится попытка возложить всю проверку данных на один слой — либо
только на ORM, либо только на бизнес-логику приложения. В результате
появляются дублирование логики, неконсистентность данных или избыточная
зависимость от конкретного инструмента доступа к данным.
В экосистеме JavaScript и TypeScript, особенно при использовании ORM
(TypeORM, Sequelize, Prisma с дополнительными слоями), часто возникает
параллельное существование двух подходов:
- валидация на уровне ORM и базы данных;
- валидация на уровне приложения (DTO, сервисы, доменная логика), где
активно используется class-validator.
Валидация на уровне ORM
Общая концепция
ORM-валидация работает ближе всего к источнику хранения данных. Её
задача — обеспечить минимально допустимую целостность объекта перед его
сохранением в базу данных.
В разных ORM этот уровень реализуется по-разному:
- через ограничения схемы базы данных;
- через встроенные валидаторы ORM;
- через хуки жизненного цикла сущностей;
- через декларативные правила в моделях.
Ограничения базы
данных как форма валидации
Наиболее фундаментальный уровень валидации — это ограничения самой
базы данных:
NOT NULL
UNIQUE
CHECK
- внешние ключи (
FOREIGN KEY)
- ограничения длины и типов (в зависимости от СУБД)
Пример логики:
- поле email не может быть
NULL;
- значение username должно быть уникальным;
- возраст должен быть больше нуля.
Эти правила невозможно обойти на уровне ORM без явного нарушения
контракта базы.
Валидация внутри ORM
Некоторые ORM предоставляют встроенные механизмы проверки данных
перед сохранением.
TypeORM
В TypeORM валидация чаще реализуется косвенно:
- через декораторы
@Column({ unique: true });
- через lifecycle hooks (
@BeforeInsert,
@BeforeUpdate);
- через ручную проверку в сервисах;
- через интеграцию с внешними библиотеками.
Сам ORM не является полноценным валидатором бизнес-правил, а скорее
обеспечивает инфраструктурные гарантии.
Sequelize
Sequelize предоставляет более явный механизм:
validate на уровне модели;
- кастомные валидаторы;
- встроенные проверки (isEmail, isInt и т.д.).
Пример концептуальной модели:
- проверка длины строки;
- проверка формата email;
- проверка диапазона значений.
Характеристики ORM-валидации
ORM-уровень обладает следующими свойствами:
- выполняется ближе к моменту записи в базу;
- зависит от структуры хранения данных;
- часто синхронно или на уровне транзакции;
- тесно связан со схемой данных.
Ограничения подхода
Валидация на уровне ORM имеет ряд системных ограничений:
- невозможность выразить сложную бизнес-логику;
- слабая интеграция с HTTP-слоем (ошибки приходят поздно);
- зависимость от конкретной ORM;
- недостаточная гибкость для комбинированных правил;
- трудность централизованного управления ошибками.
Валидация на
уровне приложения с class-validator
Общая концепция
Валидация на уровне приложения реализуется до взаимодействия с ORM и
базой данных. В экосистеме JavaScript часто используется библиотека
class-validator, особенно в связке с class-transformer и фреймворками
вроде NestJS.
Основная идея заключается в том, что данные проверяются на уровне DTO
(Data Transfer Object), ещё до попадания в доменную модель.
Декларативная модель
валидации
class-validator использует декораторы для описания правил:
@IsString()
@IsEmail()
@Length()
@IsInt()
@Min(), @Max()
@IsOptional()
Эти правила применяются к классам, которые описывают входные
данные.
Пример логики DTO
DTO выступает как контракт входящих данных:
- структура запроса описывается классом;
- правила проверок закреплены рядом с полями;
- валидация выполняется до попадания данных в бизнес-логику.
Такой подход обеспечивает раннее обнаружение ошибок и формирование
предсказуемого поведения API.
Механизм выполнения
валидации
Процесс обычно включает следующие шаги:
- Получение сырого входного объекта (JSON).
- Преобразование в экземпляр класса (class-transformer).
- Применение правил class-validator.
- Возврат ошибок или продолжение выполнения.
Ошибки формируются в структурированном виде, что позволяет
стандартизировать ответ API.
Особенности уровня
приложения
Валидация на этом уровне характеризуется следующими свойствами:
- выполняется до доступа к базе данных;
- не зависит от ORM;
- легко расширяется бизнес-логикой;
- централизуется в DTO;
- хорошо интегрируется с API-слоем.
Сравнение подходов
Момент выполнения
- ORM-валидация: перед записью в базу или во время транзакции;
- application-валидация: перед входом в бизнес-логику.
Назначение
- ORM: защита целостности данных на уровне хранения;
- application: проверка корректности входных данных и
бизнес-правил.
Гибкость
- ORM: ограниченная, зависит от возможностей ORM и СУБД;
- application: высокая, можно описывать сложные правила.
Примеры проверок
ORM-уровень:
- уникальность email;
- NOT NULL;
- ограничение длины строки;
- базовые типы.
Application-уровень:
- сложные зависимости между полями;
- условные проверки (если A, то B обязательно);
- проверка формата и структуры входного JSON;
- контекстные правила (например, роль пользователя).
Обработка ошибок
- ORM: ошибки часто приходят в виде исключений базы данных или
ORM-исключений;
- application: ошибки формируются заранее, структурированно и
предсказуемо.
Пересечение уровней и
дублирование
В реальных проектах часто возникает пересечение правил:
- email проверяется и в DTO, и в базе как UNIQUE;
- длина строки задаётся и в ORM, и в class-validator;
- обязательные поля дублируются.
Это не является избыточностью в строгом смысле — это разные уровни
защиты:
- application-уровень предотвращает некорректный запрос;
- ORM-уровень гарантирует целостность даже при обходе API.
Ошибки
проектирования при выборе уровня валидации
Перегрузка ORM логикой
Перенос сложной бизнес-валидации в ORM приводит к:
- усложнению моделей;
- трудности тестирования;
- привязке бизнес-логики к базе данных;
- снижению переносимости кода.
Игнорирование
ORM-ограничений
Отсутствие ограничений на уровне базы данных приводит к:
- возможности записи неконсистентных данных;
- уязвимости при обходе приложения;
- зависимости целостности только от кода.
Дублирование без стратегии
Простое копирование правил между слоями без разделения
ответственности создаёт:
- сложность сопровождения;
- рассинхронизацию правил;
- неоднозначность источника истины.
Разделение
ответственности между слоями
Корректная архитектура предполагает распределение правил:
ORM и база данных
- уникальность;
- целостность связей;
- ограничения типов;
- минимальные проверки структуры хранения.
Слой приложения
(class-validator)
- проверка входных DTO;
- форматирование и структура данных;
- базовая бизнес-валидация;
- предварительная защита от некорректных запросов.
Доменный слой (если
присутствует)
- сложные бизнес-инварианты;
- правила, зависящие от состояния системы;
- проверки, не привязанные к транспорту или хранению.
Практическая модель
взаимодействия слоёв
Типичный поток данных выглядит следующим образом:
- Запрос поступает в API.
- DTO проходит validation через class-validator.
- Бизнес-логика выполняет доменные проверки.
- ORM проверяет ограничения при сохранении.
- База данных гарантирует финальную целостность.
Каждый слой добавляет собственный уровень защиты, не заменяя
предыдущий.
Итоговая архитектурная
логика выбора
Распределение валидации определяется характером правил:
- если правило связано с форматом входных данных — применяется
application-уровень;
- если правило связано с хранением — применяется ORM и база
данных;
- если правило связано с состоянием системы — применяется доменный
слой.
Такое разделение снижает связанность компонентов и повышает
предсказуемость поведения системы при изменении требований.