Что такое useFactory?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
useFactory: провайдер зависимостей в NestJS
useFactory — это один из способов создания провайдеров в NestJS. Это функция, которая возвращает значение или экземпляр, используемый для инъекции зависимостей (Dependency Injection). Это не React hook, а паттерн NestJS для динамического создания сервисов.
Контекст: Провайдеры в NestJS
В NestJS провайдер — это класс или функция, которые можно инъецировать в другие классы. Есть несколько способов определить провайдер:
// 1. Простой провайдер (class)
@Injectable()
class DatabaseService {
constructor() {
this.connection = null;
}
}
// 2. useValue — статическое значение
const MY_CONFIG = {
dbUrl: 'postgres://...',
port: 5432
};
// 3. useFactory — динамическое создание
// 4. useClass — использование класса как провайдера
Что такое useFactory
useFactory позволяет динамически создавать провайдер на основе логики, конфигурации или других провайдеров:
// config.service.ts
@Injectable()
export class ConfigService {
getDatabaseUrl() {
return process.env.DATABASE_URL || 'postgres://localhost';
}
}
// database.service.ts
@Injectable()
export class DatabaseService {
constructor(private configService: ConfigService) {}
connect() {
const url = this.configService.getDatabaseUrl();
console.log('Connecting to:', url);
}
}
// app.module.ts
@Module({
providers: [
ConfigService,
{
provide: 'DATABASE_CONNECTION', // Токен провайдера
useFactory: (configService: ConfigService) => {
// Factory функция
const url = configService.getDatabaseUrl();
// Создаём и возвращаем экземпляр
return {
connect: () => console.log('Connecting to', url),
url: url
};
},
inject: [ConfigService] // Инъецируемые зависимости
},
DatabaseService
]
})
export class AppModule {}
Основные компоненты useFactory
{
provide: 'TOKEN_NAME', // 1. Токен, по которому будет инъецироваться
useFactory: (dep1, dep2) => { // 2. Factory функция, получает зависимости
// Здесь может быть сложная логика
return {
method1() { },
method2() { }
};
},
inject: [Dependency1, Dependency2] // 3. Зависимости для factory функции
}
Примеры использования useFactory
1. Подключение к БД на основе конфигурации
// database.provider.ts
import { Knex, knex } from 'knex';
export const databaseProvider = {
provide: 'DATABASE',
useFactory: async (configService: ConfigService): Promise<Knex> => {
const dbConfig = {
client: 'postgresql',
connection: configService.getDatabaseConfig(),
migrations: { directory: './migrations' }
};
const db = knex(dbConfig);
// Проверяем соединение
try {
await db.raw('SELECT 1');
console.log('Database connected');
} catch (error) {
console.error('Database connection failed', error);
throw error;
}
return db;
},
inject: [ConfigService]
};
// app.module.ts
@Module({
providers: [ConfigService, databaseProvider]
})
export class AppModule {}
// users.service.ts
@Injectable()
export class UsersService {
constructor(@Inject('DATABASE') private db: Knex) {}
findAll() {
return this.db('users').select();
}
}
2. Redis кэш с асинхронной инициализацией
// cache.provider.ts
import Redis from 'ioredis';
export const cacheProvider = {
provide: 'CACHE',
useFactory: async (configService: ConfigService): Promise<Redis> => {
const redis = new Redis({
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
password: configService.get('REDIS_PASSWORD')
});
// Проверяем соединение
await redis.ping();
console.log('Redis connected');
return redis;
},
inject: [ConfigService]
};
// posts.service.ts
@Injectable()
export class PostsService {
constructor(@Inject('CACHE') private redis: Redis) {}
async getPost(id: number) {
// Пытаемся получить из кэша
const cached = await this.redis.get(\`post:\${id}\`);
if (cached) return JSON.parse(cached);
// Если нет, получаем из БД
const post = await this.db.query('SELECT * FROM posts WHERE id = ?', [id]);
// Кэшируем на 1 час
await this.redis.setex(\`post:\${id}\`, 3600, JSON.stringify(post));
return post;
}
}
3. JWT стратегия с динамической конфигурацией
// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
// Конфигурация получается динамически
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET')
});
}
validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
// auth.module.ts
@Module({
imports: [PassportModule, JwtModule.register({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1h' }
})],
providers: [AuthService, JwtStrategy]
})
export class AuthModule {}
4. Логгер с разными уровнями на основе окружения
// logger.provider.ts
import * as winston from 'winston';
export const loggerProvider = {
provide: 'LOGGER',
useFactory: (configService: ConfigService): winston.Logger => {
const level = configService.get('LOG_LEVEL') || 'info';
return winston.createLogger({
level,
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({
filename: 'error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'combined.log'
})
]
});
},
inject: [ConfigService]
};
// app.module.ts
@Module({
providers: [ConfigService, loggerProvider]
})
export class AppModule {}
// any.service.ts
@Injectable()
export class AnyService {
constructor(@Inject('LOGGER') private logger: winston.Logger) {}
doSomething() {
this.logger.info('Doing something');
}
}
useFactory vs useClass vs useValue
// 1. useValue — простое значение
{
provide: 'APP_CONFIG',
useValue: { port: 3000, host: 'localhost' }
}
// 2. useClass — использование класса
@Injectable()
export class DefaultLogger {}
{
provide: 'LOGGER',
useClass: DefaultLogger
}
// 3. useFactory — сложная логика создания
{
provide: 'LOGGER',
useFactory: (configService: ConfigService) => {
if (configService.get('ENV') === 'production') {
return new ProductionLogger();
}
return new DevelopmentLogger();
},
inject: [ConfigService]
}
// 4. useExisting — алиас для существующего провайдера
{
provide: 'DEFAULT_LOGGER',
useExisting: 'LOGGER'
}
Асинхронные useFactory провайдеры
useFactory поддерживает асинхронность — очень полезно для инициализации при запуске приложения:
// database.provider.ts
export const databaseProvider = {
provide: 'DATABASE',
useFactory: async (configService: ConfigService): Promise<any> => {
const db = new Database(configService.getDatabaseConfig());
await db.initialize();
// Подключаем hooks для graceful shutdown
process.on('SIGTERM', async () => {
await db.close();
});
return db;
},
inject: [ConfigService]
};
// app.module.ts
@Module({
imports: [
// Нужно использовать асинхронный импорт модуля
TypeOrmModule.forRootAsync({
useFactory: async (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
database: configService.get('DB_NAME'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false
}),
inject: [ConfigService]
})
]
})
export class AppModule {}
Типичные ошибки
// ❌ Забыли inject
{
provide: 'SERVICE',
useFactory: (configService: ConfigService) => {
// configService будет undefined!
}
// inject отсутствует
}
// ✅ Правильно
{
provide: 'SERVICE',
useFactory: (configService: ConfigService) => {
return configService.getService();
},
inject: [ConfigService]
}
// ❌ useFactory должна возвращать значение
{
provide: 'SERVICE',
useFactory: (configService: ConfigService) => {
// Функция без return — вернёт undefined
configService.getService();
},
inject: [ConfigService]
}
// ✅ Правильно: явный return
{
provide: 'SERVICE',
useFactory: (configService: ConfigService) => {
return configService.getService();
},
inject: [ConfigService]
}
Когда использовать useFactory
Используйте useFactory когда:
- Нужна сложная логика создания провайдера
- Провайдер зависит от конфигурации или других сервисов
- Нужна условная инициализация (разные экземпляры для разных окружений)
- Нужна асинхронная инициализация (подключение к БД, загрузка конфига)
- Нужны побочные эффекты при создании (логирование, очистка ресурсов)
Используйте useValue когда:
- Провайдер это просто статическое значение
Используйте useClass когда:
- Провайдер это класс без сложной логики инициализации
Итоговый вывод
useFactory в NestJS — это паттерн для динамического создания провайдеров с доступом к другим сервисам и конфигурации. Это мощный инструмент для:
- Инициализации сложных сервисов
- Условного создания разных экземпляров
- Асинхронной инициализации ресурсов
- Централизации логики создания объектов
Это не React hook, а бэкенд паттерн NestJS для управления зависимостями.