КурсыNestJSТема 14

NestJS / 38 минут

Порядок обработки и завершение

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

В процессе Теория: впереди Практика: впереди Тест: впереди
14 / 14
После этой главы
  • Проследите порядок обработки запроса и корректно освобождайте ресурсы приложения.
  • Применить тему в задаче «Порядок обработки и завершение» и объяснить граничные случаи.

Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Два жизненных цикла

У приложения есть запуск, работа и завершение. У отдельного запроса есть собственный путь через middleware, guards, interceptors, pipes и контроллер. Эти процессы связаны, но не совпадают. Singleton-сервис создаётся для приложения, а его метод может вызываться многими запросами одновременно. Поле такого сервиса не становится автоматически приватным для текущего пользователя.

Понимание порядка помогает определить место правила. Проверку сессии выполняют до сценария, DTO проверяют до вызова метода, успешный ответ преобразуют после результата. Нельзя ожидать, что pipe уже преобразовал параметр, когда guard читает сырой request. Нельзя полагаться на filter для выполнения обычного бизнес-действия.

Путь запроса

В типичном HTTP-потоке сначала выполняются middleware, затем guards, входная часть interceptors, pipes и обработчик с вызванными сервисами. Результат возвращается через interceptors в обратном порядке вложенности. Необработанное исключение уходит в соответствующий filter. Отказ guard прекращает путь до контроллера.

TypeScript
// Схема обычного успешного пути:
// middleware -> guard -> interceptor (до)
// -> pipe -> controller -> service
// -> interceptor (после) -> HTTP response

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

Инициализация ресурсов

OnModuleInit вызывается после разрешения зависимостей соответствующего модуля. OnApplicationBootstrap относится к готовности всех модулей до начала прослушивания. Асинхронный hook может вернуть Promise; Nest дождётся завершения. Это полезно для проверки обязательного ресурса, но долгие миграции и фоновые задачи лучше организовывать отдельно и контролировать их время.

TypeScript
@Injectable()
class Resource implements OnModuleInit, OnModuleDestroy {
  onModuleInit() { this.client.connect(); }
  onModuleDestroy() { return this.client.close(); }
}

Если connect асинхронный, верните Promise или используйте async/await, иначе готовность будет ложной. client в примере — внедрённый адаптер. Создание HTTP-приложения ещё не означает выполнение всех hooks: app.init или app.listen запускают инициализацию. createApplicationContext инициализирует автономный контекст без HTTP.

Корректное завершение

app.close запускает завершение приложения и связанные hooks. OnModuleDestroy, beforeApplicationShutdown и onApplicationShutdown позволяют освободить ресурсы на соответствующих стадиях. Не удаляйте постоянные пользовательские данные при shutdown: речь идёт о соединениях, таймерах, подписках и временных ресурсах процесса.

TypeScript
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(settings.port);

enableShutdownHooks подключает обработку поддерживаемых сигналов ОС. Возможности Windows и Linux различаются; принудительное завершение процесса может не дать выполнить cleanup. В тестах надёжнее явно вызвать app.close в finally. Не включайте сигналовые listeners без необходимости в каждом из множества тестовых приложений одного процесса.

Фоновые задачи и состояние

При остановке важно перестать принимать новые задачи, дождаться допустимых текущих работ и закрыть инфраструктуру. У процесса должен быть ограниченный срок завершения. Бесконечное ожидание внешнего клиента делает graceful shutdown бесполезным; таймаут и политика повторов должны принадлежать соответствующему адаптеру. Незавершённые важные действия требуют надёжного хранилища или очереди, а не только массива в памяти.

TypeScript
async function inspectContext() {
  const app = await NestFactory.createApplicationContext(AppModule);
  try {
    return app.get(HealthService).check();
  } finally {
    await app.close();
  }
}

Если check асинхронный и использует ресурс контекста, здесь нужен return await, чтобы finally не закрыл его раньше завершения. Этот тонкий случай показывает, почему lifecycle связан с обычной семантикой Promise. Request-scoped провайдеры не привязаны к общему жизненному циклу приложения так же, как singleton; не поручайте им глобальное закрытие пула.

Практика журнала hooks

Создайте provider с OnModuleInit и OnModuleDestroy. Первый hook добавляет init, второй destroy в массив конкретного вызова solve. Создайте application context, закройте его и только затем верните журнал. Не используйте общий массив уровня файла: повторные запуски тогда смешивают события разных контекстов.

После решения проверьте, что журнал содержит каждое событие ровно один раз. Затем добавьте асинхронный тестовый ресурс и дождитесь его освобождения. В итоговом проекте проверьте остановку контейнера и отсутствие зависших соединений. Успешный ответ /health подтверждает доступность в момент запроса, но не доказывает корректное освобождение ресурсов при завершении процесса.

Загружаем редактор…
Ты дошёл до конца разделаЗакрепить знания в проекте

Музыка: Reverie — Scott Buckley · CC BY 4.0