Валидация в микросервисах

В микросервисной архитектуре данные перемещаются между независимыми сервисами через HTTP, очереди сообщений, event-bus и RPC-протоколы. Каждый сервис становится отдельной точкой доверия, а границы между ними — основным местом возникновения ошибок данных. Валидация перестаёт быть локальной задачей контроллера и превращается в распределённый механизм защиты контрактов.

Ключевая особенность микросервисов — отсутствие единого центра контроля данных. Любое сообщение может быть сформировано внешним клиентом, другим сервисом, фоновой задачей или брокером сообщений. Это делает валидацию не вспомогательной, а критической частью архитектуры.

Границы доверия

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

Граница доверия проходит:

  • между клиентом и API Gateway
  • между сервисами внутри кластера
  • между consumer и producer в очередях сообщений
  • между различными версиями одного и того же сервиса

Любая граница требует независимой валидации входных данных. Отсутствие проверки приводит к каскадным ошибкам, которые распространяются по цепочке сервисов.

Валидация входящих HTTP-запросов

HTTP остаётся основным транспортом для синхронных взаимодействий. Валидация здесь выполняет роль первого фильтра корректности данных.

Типичный подход — использование DTO (Data Transfer Object) и декларативных правил. Библиотека class-validator применяется для описания ограничений прямо в классах данных.

Пример DTO:

import { IsString, IsInt, Min, Max, IsEmail } from "class-validator";

export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsInt()
  @Min(18)
  @Max(120)
  age: number;
}

В микросервисах подобные DTO используются не только в контроллерах, но и в промежуточных слоях (gateway, adapters), поскольку входные данные могут приходить из разных источников.

Валидация сообщений брокеров

Асинхронные системы на базе Kafka, RabbitMQ, NATS или Redis Streams создают дополнительную сложность: сообщения могут быть доставлены повторно, в разном порядке или в устаревшем формате.

Типовой сценарий:

import { validate } from "class-validator";

async function handleMessage(payload: any) {
  const dto = Object.assign(new OrderCreatedEvent(), payload);

  const errors = await validate(dto);
  if (errors.length > 0) {
    throw new Error("Invalid event payload");
  }

  processOrder(dto);
}

В отличие от HTTP-запросов, здесь отсутствует централизованный слой валидации. Каждый consumer обязан самостоятельно проверять структуру входящего события.

DTO как контракт

В микросервисной архитектуре DTO фактически становится контрактом между системами. Он фиксирует:

  • структуру данных
  • обязательные поля
  • допустимые диапазоны значений
  • форматы строк

Использование class-validator позволяет формализовать контракт без необходимости вводить отдельную схему (например, JSON Schema), хотя оба подхода могут сосуществовать.

Пример контракта события:

import { IsUUID, IsString, IsDate } from "class-validator";

export class PaymentSucceededEvent {
  @IsUUID()
  paymentId: string;

  @IsString()
  status: string;

  @IsDate()
  processedAt: Date;
}

В распределённой системе такие классы часто дублируются между сервисами, что создаёт необходимость синхронизации версий.

Использование Class-validator в инфраструктурных слоях

В микросервисах валидация часто переносится за пределы контроллеров:

API Gateway слой

Gateway выполняет первичную фильтрацию запросов:

  • проверка обязательных полей
  • базовая типизация
  • защита от очевидно некорректных данных
async function gatewayMiddleware(req, res, next) {
  const dto = Object.assign(new CreateUserDto(), req.body);
  const errors = await validate(dto);

  if (errors.length) {
    return res.status(400).json(errors);
  }

  next();
}

Сервисный слой

Сервисная валидация более строгая и учитывает бизнес-правила:

  • уникальность данных (в сочетании с БД)
  • допустимые переходы состояний
  • проверка зависимостей между полями
export class UpdateOrderDto {
  @IsString()
  status: string;

  @IsString()
  orderId: string;
}

Даже если gateway уже проверил данные, сервис не должен полагаться на его корректность.

Defensive validation

Защитная валидация применяется на всех уровнях как способ изоляции от неконсистентных данных:

  • повторная проверка входных событий
  • защита от устаревших контрактов
  • фильтрация частично повреждённых сообщений

Паттерны применения в распределённых системах

Двойная валидация (edge + core)

Валидация выполняется дважды:

  1. на границе системы (gateway)
  2. внутри микросервиса

Это уменьшает вероятность проникновения некорректных данных в бизнес-логику.

Event-driven validation

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

async function onEvent(event: unknown) {
  const dto = plainToInstance(UserRegisteredEvent, event);
  await validateOrReject(dto);
}

Schema drift protection

В распределённых системах изменения контрактов происходят асинхронно. Один сервис может уже публиковать новый формат, в то время как другой ещё использует старый.

Валидация помогает обнаруживать:

  • отсутствующие поля
  • изменённые типы
  • неожиданные значения

Синхронизация контрактов между сервисами

Одной из ключевых проблем является дублирование DTO в разных сервисах. При использовании class-validator это особенно заметно, поскольку декораторы привязаны к TypeScript-классам.

Типичные подходы:

  • выделение общей библиотеки контрактов
  • генерация DTO из схем
  • версионирование событий (v1, v2, v3)

Общий пакет контрактов снижает риск рассинхронизации, но вводит зависимость между сервисами.

Ошибки валидации и деградация системы

В микросервисах ошибка валидации — это не локальное событие, а потенциальная точка деградации всей цепочки обработки.

Типовые сценарии:

  • сообщение попадает в DLQ (dead-letter queue)
  • запрос отклоняется на gateway
  • сервис повторяет обработку некорректного события
  • каскадные ошибки в downstream-сервисах

Стратегии обработки:

  • явное логирование причин валидации
  • изоляция повреждённых сообщений
  • ограничение повторных попыток обработки

Производительность и масштабирование

Валидация на основе рефлексии и декораторов имеет стоимость. В высоконагруженных микросервисах это становится заметным фактором.

Основные оптимизации:

  • минимизация количества валидируемых полей
  • кэширование трансформаций DTO
  • предварительная фильтрация «пустых» или явно некорректных payload
  • разделение лёгкой и тяжёлой валидации

Особенно важно учитывать, что асинхронные consumers могут обрабатывать тысячи сообщений в секунду, и каждая проверка добавляет накладные расходы.

Антипаттерны в распределённой валидации

Полное доверие внутренним сервисам

Игнорирование валидации на внутренних границах приводит к распространению некорректных данных по системе.

Единая точка валидации

Попытка централизовать проверку в одном сервисе приводит к:

  • сильной связанности компонентов
  • снижению отказоустойчивости
  • усложнению эволюции контрактов

Дублирование без синхронизации

Копирование DTO между сервисами без версионирования приводит к расхождению логики проверки и фактических данных.

Перегруженные DTO

Добавление бизнес-логики в DTO приводит к нарушению разделения ответственности. Валидация должна оставаться декларативной, а не вычислительной.

В распределённых системах class-validator используется как инструмент описания ограничений, а не как слой бизнес-логики, что критично для поддерживаемости микросервисной архитектуры.