Параллельное выполнение

Библиотека Vest реализует декларативную модель валидации, вдохновлённую подходом unit-тестирования. Каждая проверка рассматривается как изолированный тест, а набор проверок формирует suite. Исполнение suite происходит через вызов runner-функции, которая управляет порядком, условиями выполнения и асинхронностью.

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


Синхронные и асинхронные проверки

Синхронные проверки выполняются последовательно в рамках текущего потока исполнения. Их результат фиксируется немедленно, без ожидания.

import { create, test, enforce } fr om 'vest';

const suite = create((data = {}) => {
  test('username', 'Имя обязательно', () => {
    enforce(data.username).isNotEmpty();
  });

  test('age', 'Возраст должен быть числом', () => {
    enforce(data.age).isNumber();
  });
});

Асинхронные проверки добавляют возможность обращения к внешним источникам: API, базам данных, удалённым сервисам.

test('username_unique', 'Имя уже занято', async () => {
  const exists = await api.checkUsername(data.username);
  enforce(exists).equals(false);
});

Асинхронность становится базовым механизмом, на котором строится параллельное выполнение.


Асинхронная модель исполнения и конкурентность

При наличии нескольких асинхронных проверок Vest не требует их последовательного ожидания. Внутри одного прохода suite такие проверки могут быть запущены одновременно, если они не зависят друг от друга.

Типовая реализация конкурентного поведения основана на запуске нескольких Promise одновременно с последующим агрегированием результатов.

const checks = [
  api.checkUsername(data.username),
  api.checkEmail(data.email),
  api.checkProfile(data.profileId),
];

const results = await Promise.all(checks);

Такая модель снижает общее время выполнения валидации, особенно при большом количестве независимых запросов.


Параллелизм на уровне suite

В Vest параллелизм проявляется не как отдельная сущность, а как следствие организации async-проверок. Каждая независимая ветка suite может выполняться одновременно с другими, если отсутствуют зависимости по данным.

При построении сложных схем валидации suite фактически разбивается на несколько групп проверок:

  • локальные синхронные правила;
  • асинхронные проверки уникальности;
  • внешние API-валидации;
  • условные проверки, зависящие от частичных результатов.
const suite = create(async (data) => {
  test('email_format', () => {
    enforce(data.email).matches(/.+@.+/);
  });

  test('email_unique', async () => {
    const taken = await api.emailExists(data.email);
    enforce(taken).equals(false);
  });

  test('profile_valid', async () => {
    const profile = await api.getProfile(data.profileId);
    enforce(profile.active).isTruthy();
  });
});

Здесь асинхронные тесты могут выполняться конкурентно, не блокируя друг друга.


Управление порядком и зависимостями

Параллельное выполнение требует контроля зависимостей между проверками. Если одна проверка опирается на результат другой, возникает необходимость сериализации.

Пример зависимости:

test('load_profile', async () => {
  const profile = await api.getProfile(data.profileId);
  return profile;
});

test('check_profile_permissions', async () => {
  const profile = await api.getProfile(data.profileId);
  enforce(profile.canEdit).isTruthy();
});

В подобных случаях повторный запрос можно избежать через кэширование промежуточного результата внутри scope suite:

let cachedProfile;

test('load_profile', async () => {
  cachedProfile = await api.getProfile(data.profileId);
});

test('check_profile_permissions', () => {
  enforce(cachedProfile.canEdit).isTruthy();
});

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


Параллелизм и группировка проверок

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

const suite = create(async (data) => {
  // Группа 1: быстрые синхронные проверки
  test('required_fields', () => {
    enforce(data.email).isNotEmpty();
    enforce(data.password).isNotEmpty();
  });

  // Группа 2: независимые async-запросы
  test('email_unique', async () => api.emailExists(data.email));
  test('username_unique', async () => api.usernameExists(data.username));
  test('phone_unique', async () => api.phoneExists(data.phone));

  // Группа 3: сложная серверная валидация
  test('account_state', async () => api.checkAccount(data.userId));
});

Такое разделение позволяет рантайму эффективно распределять выполнение между async-задачами.


Оптимизация через агрегацию запросов

Одним из ключевых способов повышения производительности является объединение нескольких асинхронных проверок в один запрос.

test('uniqueness_checks', async () => {
  const result = await api.checkBatch({
    email: data.email,
    username: data.username,
    phone: data.phone,
  });

  enforce(result.emailTaken).equals(false);
  enforce(result.usernameTaken).equals(false);
  enforce(result.phoneTaken).equals(false);
});

В контексте параллельного выполнения это уменьшает количество отдельных Promise и снижает сетевые накладные расходы.


Изоляция состояния и конкурентные гонки

Параллельное выполнение асинхронных проверок требует строгой изоляции состояния suite. Общие переменные могут стать источником race conditions.

let user;

test('load_user', async () => {
  user = await api.getUser(data.id);
});

test('check_role', () => {
  enforce(user.role).equals('admin');
});

При конкурентном выполнении таких тестов возможна ситуация, когда check_role выполнится до завершения load_user.

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

test('user_flow', async () => {
  const user = await api.getUser(data.id);

  enforce(user.role).equals('admin');
  enforce(user.active).isTruthy();
});

Таким образом устраняется необходимость внешней синхронизации.


Детерминизм выполнения

Несмотря на наличие параллельных операций, итоговый результат suite должен оставаться детерминированным. Это означает:

  • одинаковый набор ошибок при одинаковых входных данных;
  • отсутствие зависимости результата от порядка завершения async-задач;
  • стабильная агрегация результатов.

Для этого используется механизм сбора результатов после завершения всех промисов, независимо от их порядка завершения.


Ограничения конкурентного выполнения

Параллельное выполнение в контексте валидации ограничивается рядом факторов:

  • зависимость проверок от общего состояния;
  • необходимость раннего прерывания при критических ошибках;
  • ограничения внешних API по rate lim it;
  • стоимость одновременных запросов.

В таких случаях применяется частичная сериализация:

test('critical_check', async () => {
  const valid = await api.criticalValidation(data);
  if (!valid) return;
});

или условный запуск последующих проверок на основе промежуточного результата suite.


Стратегии балансировки нагрузки

Эффективная организация параллельного выполнения требует балансировки между скоростью и стабильностью:

  • группировка независимых async-проверок;
  • минимизация количества сетевых вызовов;
  • использование кэширования результатов;
  • исключение повторных запросов внутри одного прохода suite.

Эти стратегии позволяют удерживать производительность даже при большом количестве проверок без деградации времени отклика.