Валидация входящих данных в приложении обычно концентрируется на
уровне внешнего интерфейса системы — HTTP-запросах, очередях сообщений,
событиях и любых других источниках данных, находящихся вне доверенной
среды. Библиотека class-validator применяется прежде всего
как механизм декларативного описания ограничений на структуру
данных.
Ключевая характеристика этого уровня — недоверие к входным данным. Любой объект, пришедший извне, рассматривается как потенциально некорректный, независимо от источника. Валидационные правила здесь выполняют роль фильтра, отсеивающего структурные и семантические ошибки до попадания данных в бизнес-логику.
Наиболее распространённая практика размещения валидации в связке с
class-validator — использование DTO (Data Transfer Object).
DTO выступает промежуточной структурой между внешним миром и внутренней
моделью приложения.
В рамках такого подхода правила описываются прямо в классе:
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
DTO концентрирует в себе ограничения формата данных, не затрагивая бизнес-логику. Это разделение позволяет изолировать синтаксическую и базовую семантическую проверку от предметной области.
Важная особенность: DTO не является частью доменной модели. Его назначение ограничено транспортировкой и первичной проверкой данных.
Доменные сущности в корректно структурированном приложении не должны
напрямую зависеть от механизма валидации входящих данных. Привязка
доменной модели к class-validator приводит к смешению
ответственности: правила валидации начинают отражать не только формат
входных данных, но и внутренние бизнес-инварианты, что усложняет
сопровождение.
Доменная модель должна оставаться независимой от инфраструктурных библиотек. Валидация в домене реализуется другими средствами — через явные проверки в методах, фабриках или через доменные сервисы.
DTO при этом выполняет функцию адаптера между внешним представлением данных и внутренней моделью.
В некоторых архитектурных подходах декораторы
class-validator размещаются непосредственно в сущностях,
особенно при использовании ORM (например, TypeORM).
import { Entity, Column } from 'typeorm';
import { IsEmail } from 'class-validator';
@Entity()
export class User {
@Column()
@IsEmail()
email: string;
}
Такой подход создаёт эффект двойного назначения класса: он одновременно является и моделью хранения данных, и объектом валидации.
Подобная схема приводит к нескольким структурным последствиям:
В более строгих архитектурах сущности рассматриваются как чистые доменные объекты без инфраструктурных аннотаций.
В архитектурах, где используется промежуточный слой обработки входящих данных (например, в NestJS), валидация обычно переносится в специализированные механизмы — pipes.
Пайпы выполняют роль трансформаторов и валидаторов входных данных до попадания в контроллер.
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(new ValidationPipe());
В этой модели DTO остаётся единственным местом описания правил, а выполнение валидации централизуется на уровне инфраструктуры. Контроллеры при этом не содержат логики проверки данных, что поддерживает их чистоту и предсказуемость.
Такое разделение создаёт устойчивую границу:
class-validator позволяет создавать пользовательские
ограничения через ValidatorConstraint.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
@ValidatorConstraint({ name: 'isEven', async: false })
export class IsEvenConstraint implements ValidatorConstraintInterface {
validate(value: number) {
return value % 2 === 0;
}
}
Вопрос размещения кастомных валидаторов связан с их природой:
Характерной практикой является выделение директории
validators или constraints, которая не
привязана к конкретным DTO.
Бизнес-сервисы обычно содержат прикладную логику и операции над
доменными объектами. Размещение class-validator внутри
сервисов приводит к смешению уровней абстракции.
Проблема проявляется в нескольких аспектах:
Сервисы могут выполнять проверки, но они относятся к доменным инвариантам, а не к структурной валидации входных DTO.
Валидация может располагаться на уровне middleware, однако такой подход обычно ограничивается простыми проверками структуры запроса, заголовков или базовых параметров.
Middleware не является подходящим местом для сложных схем
class-validator, поскольку:
В результате middleware чаще используется как дополнительный слой защиты, а не основная зона валидации.
При масштабировании системы возникает проблема дублирования правил. Одни и те же ограничения могут требоваться в разных DTO.
Решение заключается в выносе повторяющихся правил в составные декораторы:
import { applyDecorators } from '@nestjs/common';
import { IsString, MinLength } from 'class-validator';
export function IsStrongPassword() {
return applyDecorators(
IsString(),
MinLength(8),
);
}
Такая композиция позволяет:
Доменные объекты содержат инварианты, которые не всегда выражаются через простые аннотации. Например, правило «баланс не может быть отрицательным после операции» относится к бизнес-логике, а не к формату данных.
Размещение таких проверок в DTO приводит к ошибочной модели ответственности: транспортный слой начинает управлять доменными ограничениями.
Корректная структура разделяет уровни:
Использование class-validator в домене создаёт скрытую
зависимость от рефлексии и декораторов. Это влияет на тестируемость и
переносимость кода.
Чистая доменная модель обычно не содержит:
Валидация в таком случае выполняется либо на этапе создания объекта, либо через отдельные валидирующие сервисы.
Логика, реализованная через class-validator, тестируется
отдельно от бизнес-кода. В зависимости от места размещения тесты имеют
различный фокус:
Размещение валидаторов рядом с DTO упрощает тестирование, поскольку структура и правила находятся в одном контексте.
При росте приложения структура валидации начинает играть ключевую роль в поддерживаемости кода. Смешение слоёв приводит к следующим эффектам:
Разделение на DTO, домен, инфраструктуру и слой кастомных ограничений
формирует устойчивую архитектурную схему, в которой
class-validator остаётся инструментом строго одного уровня
— проверки структуры входных данных.