Функция для создания модуля NestJS-mod
Функция createNestModule для создания динамического модуля NestJS с возможностью настройки через конфигурации или переменные окружения, а также предоставляющая возможность использования части сервисов модуля через метод forFeature или передачи части конфигураций из feature модуля.
Все модули имеют возможность создания нескольких параллельных именованных экземпляров модуля с различными входными параметрами; для этого нужно передать имя экземпляра в опцию contextName.
В отличие от модулей NestJS, модули NestJS-mod могут содержать дополнительные методы-обертки, которые будут вызываться при построении приложения NestJS.
Пример создания нативного модуля NestJS с использованием функции createNestModule и передачи различных типов конфигураций
import {
ConfigModel,
ConfigModelProperty,
EnvModel,
EnvModelProperty,
createNestModule,
getNestModuleDecorators,
InjectableFeatureConfigurationType,
} from "@nestjs-mod/common";
import { Injectable } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { IsNotEmpty } from "class-validator";
// App1Module
const { InjectFeatures } = getNestModuleDecorators({
moduleName: "App1Module",
});
@ConfigModel()
class AppFeatureConfig {
@ConfigModelProperty()
@IsNotEmpty()
featureOptionConfig!: string;
}
@Injectable()
class AppFeaturesService {
constructor(
@InjectFeatures()
private readonly appFeatureConfigs: InjectableFeatureConfigurationType<AppFeatureConfig>[]
) {}
getFeatureConfigs() {
return this.appFeatureConfigs.map(
({ featureConfiguration }) => featureConfiguration
);
}
}
const { App1Module } = createNestModule({
moduleName: "App1Module",
sharedProviders: [AppFeaturesService],
featureConfigurationModel: AppFeatureConfig,
});
@ConfigModel()
class App2Config {
@ConfigModelProperty()
@IsNotEmpty()
option!: string;
}
@Injectable()
class App2Service {
constructor(
private readonly appFeaturesService: AppFeaturesService,
private readonly app2Config: App2Config
) {}
getFeatureConfigs() {
return this.appFeaturesService.getFeatureConfigs();
}
getConfig() {
return this.app2Config;
}
}
// App2Module
const { App2Module } = createNestModule({
moduleName: "App2Module",
imports: [
App1Module.forFeature({
featureModuleName: "App2Module",
featureConfiguration: { featureOptionConfig: "featureOptionConfig-app2" },
}),
],
providers: [App2Service],
configurationModel: App2Config,
});
@EnvModel()
class App3Env {
@EnvModelProperty()
@IsNotEmpty()
option!: string;
}
@Injectable()
class App3Service {
constructor(
private readonly appFeaturesService: AppFeaturesService,
private readonly app3Env: App3Env
) {}
getFeatureConfigs() {
return this.appFeaturesService.getFeatureConfigs();
}
getEnv() {
return this.app3Env;
}
}
const { App3Module } = createNestModule({
moduleName: "App3Module",
imports: [
App1Module.forFeature({
featureModuleName: "App2Module",
featureConfiguration: { featureOptionConfig: "featureOptionConfig-app3" },
}),
],
providers: [App3Service],
environmentsModel: App3Env,
});
// Test
const { AppModule } = createNestModule({
moduleName: "AppModule",
imports: [
App1Module.forRoot(),
App2Module.forRoot({ configuration: { option: "appConfig3value" } }),
App3Module.forRoot({ environments: { option: "appEnv2value" } }),
],
});
async function bootstrap() {
const app = await NestFactory.create(AppModule.forRoot());
const appFeatureScannerService = app.get(AppFeaturesService);
const app2Service = app.get(App2Service);
const app3Service = app.get(App3Service);
console.log(appFeatureScannerService.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app2Service.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app3Service.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app2Service.getConfig()); // output: { option: 'appConfig3value' }
console.log(app3Service.getEnv()); // output: { option: 'appEnv2value' }
}
bootstrap();
Методы-обертки
- preWrapApplication - вызывается для всех корневых модулей, здесь мы можем создать еще один дополнительный корневой модуль, конфигурация для которого будет динамически сгенерирована на основе исходного модуля, например: установить новый префикс к имени ключей при работе с переменными окружения (пример кода)
- wrapApplication - метод, который может создать приложение или микросервис NestJS, возвращает экземпляр созданного приложения. (пример кода)
- postWrapApplication - этот метод работает после со здания приложения, например, нужно начать прослушивание на HTTP порту (пример кода)
При создании модуля можно передать множество различных новых параметров, есть как оригинальные опции NestJS, так и расширенные.
Описания и примеры использования каждой опции будут рассмотрены в отдельных постах; сейчас я опишу только опции настройки модуля.
Опции создания модуля NestJS-mod
environmentsModel
Класс и его свойства помечены декораторами типа "Переменные окружения" (Env model), он содержит свойства с примитивными типами, используемыми в модуле, значения которых могут быть получены из различных источников, таких как: process.env или consul-kv.
configurationModel
Класс и его свойства помечены декораторами типа "Config model"; описаны свойства примитивных и сложных типов, которые используются в модуле, значения для которых должны быть переданы при подключении модуля к приложению, в коде.
staticEnvironmentsModel
Класс "Переменные окружения" (Env model) со статическими свойствами примитивного типа может использоваться в момент генерации метаданных модуля; значения могут быть получены из различных источников, например: process.env или consul-kv.
Пример: различные условия импорта в зависимости от переменных окружения, динамические маршруты для REST контроллеров.
staticConfigurationModel
Класс конфигурации со статическими свойствами примитивных и сложных типов, которые могут использоваться при генерации метаданных модуля (imports, controllers); значения для них должны быть переданы при подключении модуля к приложению.
featureEnvironmentsModel
Переменные окружения - "feature" модулей с примитивными типами, значения которых могут быть получены из различных источников, таких как: process.env или consul-kv.
Пример: имя переменной окружения для подключения к базе данных feature модуля отличается от имени переменной окружения корневого подключения.
featureConfigurationModel
Класс для "feature" модулей, переменные примитивных и сложных типов, которые могут быть добавлены в текущий модуль из других модулей.
Пример: транспорт для отправки сообщения может быть определен как функциональность продукта "feature", но основная реализация обхода получателей и отправки будет "core" или "integration" модулем.
Создание главного динамического модуля и передача асинхронной конфигурации
Когда конфигурация модуля неизвестна заранее, мы можем передать ее с помощью асинхронной фабрики; если для ее работы нужны другие модули, мы можем передать их через опцию imports, как в обычном NestJS.
Помимо асинхронной фабрики, вы также можете использовать класс и передать по значению, как в обычном NestJS.
У NestJS-mod есть еще один способ передачи конфигурации - это передача потока Observable со значениями этой конфигурации. Этот метод нужен, когда значения могут меняться со временем.
Пример: core модуль для динамического изменения адреса и учетных данных прокси-сервера, когда текущий заблокирован, feature модуля может не вызывать дополнительный метод для получения текущего адреса и учетных данных, он просто использует экземпляр конфигурации, который был подключен через конструктор, как будто это статическая конфигурация.
Работа с опциями, которые были переданы из других модулей (configuration feature)
Поскольку во время инициализации модулей NestJS порядок загрузки (разрешения) может отличаться, точно на момент старта мы можем получить все "configuration features" только в хуке NestJS onApplicationBootstrap.
Для его получения нужно использовать декоратор InjectFeatures.
Когда приложение запущено (runtime), больше нет проблем с доступом ко всем конфигурациям.
Декораторы для работы с сущностями модуля
Поскольку использование декораторов - это статический код и не может быть изменено в реальном времени, декораторы для каждого модуля должны быть созданы вручную с использованием функции getNestModuleDecorators
Типы декораторов:
InjectService
Для по дключения провайдера с использованием инжектированного токена или класса.
Пример: если в приложении есть несколько экземпляров одного и того же модуля, но с разными именами контекста, то имя этого контекста можно передать в декоратор.
InjectFeatures
Для подключения массива со всеми конфигурациями, полученными от различных модулей.
Пример: Есть модуль доступности веб-сайта "systems" и есть configuration feature, через который "core" модуль для работы с базой данных может уведомлять о функциональности базы данных. На основе этого списка "systems" модуль решает, можно ли пускать людей в бэкенд.
InjectAllFeatures
Если в приложении есть несколько экземпляров модуля с разными контекстами, то для получения всех "feature configurations" нужно использовать этот декоратор.
InjectFeatureEnvironments
Помимо конфигураций, вы также можете получить все переменные окружения, которые использовали feature модули.
Пример: некое консольное приложение, которое при запуске создаст все необходимые базы данных на сервере базы данных, мы получаем строку подключения к базе данных с корневыми правами из "systems" модуля, а строка подключения к базе данных самих feature модулей передается из самих feature модулей и при этом все имена ключей разные и должны быть валидированы при запуске приложения.
InjectAllFeatureEnvironments
То же самое, что и InjectFeatureEnvironments, только собирает информацию по всем экземплярам модуля.
InjectModuleSettings
Иногда нужно получить все метаданные классов конфигурации и классов для переменных окружения модуля, это декоратор для этого.
Пример: приложение собирает все конфигурационные значения переменных окружения с именами ключей и генерирует отчет.
InjectAllModuleSettings
Независимый от контекста доступ ко всем метаданным конфигурации модуля.