Модули в Nest JS - 1.2
-
Модуль — это важная часть приложения на Nest. Каждое приложение начинается с корневого модуля, который Nest использует для создания структуры приложения. Эта структура помогает управлять зависимостями между модулями и провайдерами.
На практике это проще, чем кажется. Модули позволяют разработчикам разбивать приложение на отдельные части. Представьте, что каждый модуль — это компонент вашего приложения. Например, один модуль может отвечать за управление заказами в интернет-магазине, а другой — за управление пользователями. Вместе эти компоненты создают функциональное приложение.
Важно помнить, что модули в Nest не обязательно должны быть отдельными файлами. Это всего лишь абстракция, которая помогает структурировать приложение. Некоторые модули могут быть большими, другие — маленькими. Используя паттерн «композиция», разработчики могут создавать сложные модули из простых.
Хотя Nest позволяет создать приложение с одним модулем, это подходит только для очень простых проектов. С увеличением сложности приложения становится очевидным, что использование нескольких модулей — более эффективный подход.
Примечание: Абстракция «Модуль» в Nest реализует одноимённый структурный паттерн проектирования. Модуль объединяет данные, функции и физические компоненты в одну сущность, а взаимодействовать с ней можно через публичный интерфейс.
Как работать с модулями в Nest
В Nest модуль — это класс, помеченный декоратором @Module. Этот декоратор содержит метаданные, которые Nest использует для организации приложения.
Декоратор @Module принимает объект, который описывает структуру модуля. В этом объекте можно указать провайдеры и контроллеры модуля, а также модули, от которых он зависит, и провайдеры, которые он экспортирует. Вот основные свойства:
Свойство объекта Описание providers
Список провайдеров модуля. controllers
Список контроллеров модуля. imports
Список импортируемых модулей. exports
Список экспортируемых провайдеров модуля. Особое внимание стоит уделить свойствам imports и exports.
-
imports
: здесь указываются модули, которые нужны текущему модулю. Если модуль использует функциональность других модулей, их нужно перечислить в этом свойстве. Импортированные модули могут использовать экспортируемые провайдеры. -
exports
: в этом свойстве перечисляются провайдеры, которые будут доступны другим модулям при импорте. Это позволяет делиться функциональностью между модулями.
По умолчанию все провайдеры модуля скрыты. Чтобы использовать провайдер в другом модуле, его нужно экспортировать через свойство
exports
. Таким образом, экспортируемые провайдеры можно считать публичным интерфейсом модуля.Модули на практике
Чтобы понять, как работают модули в Nest, рассмотрим пример простого приложения — API для управления пользователями. В этом приложении нам понадобятся функции для запуска сервера и управления пользователями.
Для REST API создадим контроллер
UserController
, который будет обрабатывать маршруты. Если у нас есть бизнес-логика и взаимодействие с базой данных, нам понадобится модульUserService
, который будет реализовывать эту логику. Также потребуется описание сущности «Пользователь», например,UserEntity
.Каждый из этих элементов будет храниться в отдельном файле, что делает структуру проекта более удобной и понятной. Примерная схема файловой структуры может выглядеть следующим образом:
Давайте рассмотрим, что общего у контроллера, сервиса и сущности — все они относятся к одной области, связанной с управлением пользователями. Такие области называются доменами.
Примечание: Домен — это предметная область, которая включает бизнес-логику, процессы, данные и терминологию, связанные с этой областью.
Поскольку все эти элементы связаны, имеет смысл объединить их в функциональный модуль. Модуль «Пользователи» будет отвечать за работу с пользователями и предоставит публичный интерфейс для взаимодействия с другими модулями. Разработчик сможет использовать функциональность модуля, не углубляясь в детали его реализации.
С ростом приложения появятся и другие модули, например, для управления заказами или отправкой уведомлений. Разделение на функциональные модули упростит поддержку и установит четкие границы для каждого из них.
Примечание: Nest CLI позволяет быстро создать модуль с помощью команды:
nest g module users
.Пример организации кода для нашего модуля может выглядеть так:
import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; @Module({ controllers: [UserController], providers: [UserService], }) export class UserModule {}
Новый модуль описывается как класс, например,
UserModule
, и помечается декоратором@Module
. В этот декоратор передаётся объект с настройками модуля, где мы регистрируем контроллеры и провайдеры.Наш модуль пока ничего не экспортирует, но при необходимости можно добавить сервисы для использования в других модулях. Для этого в объект настроек нужно добавить свойство
exports
. Мы рассмотрим это на практике позже.Примечание: Хороший модуль должен решать одну конкретную задачу. Не стоит объединять всю функциональность приложения в одном модуле. Лучше разбивать на несколько, минимизируя зависимости между модулями.
Для каждого модуля обычно создаётся отдельная директория, где размещаются все его компоненты: сервисы, контроллеры и вспомогательные скрипты. Это позволяет удобно организовать код и иметь всё, что связано с модулем, в одном месте.
Подключение модуля
Чтобы использовать функциональность модуля, его нужно подключить в другом модуле. Как уже упоминалось, любое приложение на Nest состоит как минимум из одного модуля, который называется корневым (обычно
AppModule
). Чтобы активировать функциональностьUserModule
, его необходимо подключить к корневому модулю приложения.// Файл app.module.ts import { Module } from '@nestjs/common'; import { UserModule } from './cats/user.module'; @Module({ imports: [UserModule], }) export class AppModule {}
После подключения модуля
UserModule
, его функциональность становится активной. Обработчики маршрутов в контроллереUserController
будут срабатывать при совпадении маршрута.Переиспользование модулей
Создание и подключение модулей в Nest довольно просто. Разработчику нужно лишь следовать публичным интерфейсам. Nest автоматически управляет экземплярами классов модулей, и вам не нужно создавать их вручную.
Все модули в Nest по умолчанию являются синглтонами. Это значит, что один и тот же экземпляр провайдера можно использовать в разных модулях. Таким образом, каждый модуль может быть повторно использован другими модулями.
Например, если нужно разделить экземпляр
UserService
между модулями, просто экспортируйте этот провайдер изUserModule
. Для этого добавьте соответствующий ключ в объект настроек модуля:import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; @Module({ controllers: [UserController], providers: [UserService], // Добавляем экспорт провайдера exports: [UserService] }) export class UserModule {}
Теперь любой модуль, который импортирует
UserModule
, получает доступ кUserService
, используя один и тот же экземпляр.Реэкспорт модулей
Модули могут экспортировать не только свои провайдеры, но и другие модули, которые они импортируют. Это называется реэкспортом модулей и значительно расширяет возможности для композиции. В коде реэкспорт модулей работает так же, как экспорт провайдеров. Рассмотрим это на примере.
@Module({ imports: [FooModule], exports: [FooModule] }) export class UserModule {}
Принцип остаётся прежним: в
exports
можно перечислять как провайдеры, так и модули для реэкспорта. В приведённом примереUserModule
импортируетFooModule
и реэкспортирует его, делая доступным для других модулей, которые импортируютUserModule
.Внедрение зависимостей
В класс модуля можно внедрять провайдеров как зависимости через конструктор. Это может быть полезно при настройке модуля. Рассмотрим это на примере:
import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; @Module({ controllers: [UserController], providers: [UserService], }) export class UserModule { // UserService внедряется с помощью DI constructor(private userService: UserService) {} }
Важно помнить, что классы модулей не могут быть провайдерами из-за возможных циклических зависимостей.
Глобальные модули
В приложении часто появляются модули, которые нужно импортировать в разных местах, например, хелперы или модули для работы с базой данных. Чтобы не повторять процедуру импорта, Nest позволяет создавать глобальные модули. Все провайдеры таких модулей будут доступны во всем приложении.
Чтобы сделать модуль глобальным, нужно аннотировать его декоратором
@Global
. Глобальные модули регистрируются только один раз, обычно в корневом модуле приложения.Рассмотрим пример с модулем
UserModule
. Аннотируем его декоратором@Global
, и теперь провайдерUserService
станет доступен другим модулям для внедрения без необходимости импортироватьUserModule
.import { Module, Global } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service'; // Сделаем модуль глобальным @Global() @Module({ controllers: [UserController], providers: [UserService], // Экспортируемые провайдеры // доступны другим модулям для внедрения // в качестве зависимости без необходимости // импортировать UserModule. exports: [UserService], }) export class UserModule {}
Хотя создание глобальных модулей удобно, использовать эту возможность нужно осторожно. Не стоит делать все модули глобальными, так как это может привести к сильной связанности между ними. Когда приложение вырастет, будет трудно отслеживать связи между модулями, и рефакторинг станет сложным. Используйте глобальные модули с осторожностью.
Заключение
Модуль — это ключевая абстракция в Nest. Его можно сравнить с компонентом, отвечающим за определённую функциональность приложения. Мы рассмотрели, как объявлять модули, использовать декоратор @Module, выполнять реэкспорт модулей и другие аспекты.
Любое Nest-приложение состоит минимум из одного модуля, но не стоит складывать всю функциональность в один модуль. Разделение на модули упрощает поддержку и выделяет функциональность в отдельные компоненты.
-