Валидация на уровне ORM vs на уровне приложения

Роль валидации в многослойной архитектуре

Валидация данных существует на разных уровнях системы, и каждый уровень решает собственный класс задач. Ошибкой проектирования становится попытка возложить всю проверку данных на один слой — либо только на 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.


Механизм выполнения валидации

Процесс обычно включает следующие шаги:

  1. Получение сырого входного объекта (JSON).
  2. Преобразование в экземпляр класса (class-transformer).
  3. Применение правил class-validator.
  4. Возврат ошибок или продолжение выполнения.

Ошибки формируются в структурированном виде, что позволяет стандартизировать ответ 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;
  • форматирование и структура данных;
  • базовая бизнес-валидация;
  • предварительная защита от некорректных запросов.

Доменный слой (если присутствует)

  • сложные бизнес-инварианты;
  • правила, зависящие от состояния системы;
  • проверки, не привязанные к транспорту или хранению.

Практическая модель взаимодействия слоёв

Типичный поток данных выглядит следующим образом:

  1. Запрос поступает в API.
  2. DTO проходит validation через class-validator.
  3. Бизнес-логика выполняет доменные проверки.
  4. ORM проверяет ограничения при сохранении.
  5. База данных гарантирует финальную целостность.

Каждый слой добавляет собственный уровень защиты, не заменяя предыдущий.


Итоговая архитектурная логика выбора

Распределение валидации определяется характером правил:

  • если правило связано с форматом входных данных — применяется application-уровень;
  • если правило связано с хранением — применяется ORM и база данных;
  • если правило связано с состоянием системы — применяется доменный слой.

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