Appearance
NestJS提供者
提供者(Providers)是NestJS的基本构建块,几乎所有的类都可以被视为提供者,如服务、仓库、工厂、助手等。提供者可以被注入到构造函数中,使得模块之间的依赖关系更加清晰和灵活。
提供者基础
服务提供者
使用@Injectable()装饰器标记提供者:
typescript
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
findOne(id: string): Cat {
return this.cats.find(cat => cat.id === id);
}
update(id: string, cat: Cat): Cat {
const index = this.cats.findIndex(c => c.id === id);
if (index !== -1) {
this.cats[index] = { ...this.cats[index], ...cat };
return this.cats[index];
}
return null;
}
remove(id: string): boolean {
const index = this.cats.findIndex(cat => cat.id === id);
if (index !== -1) {
this.cats.splice(index, 1);
return true;
}
return false;
}
}
在控制器中使用提供者
typescript
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: Cat) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<Cat> {
return this.catsService.findOne(id);
}
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateCatDto: Cat,
): Promise<Cat> {
return this.catsService.update(id, updateCatDto);
}
@Delete(':id')
async remove(@Param('id') id: string): Promise<boolean> {
return this.catsService.remove(id);
}
}
提供者类型
1. 值提供者 (Value Providers)
typescript
// 值提供者 - 直接提供一个值
export const configProvider = {
provide: 'CONFIG',
useValue: {
port: process.env.PORT || 3000,
host: process.env.HOST || 'localhost',
database: {
url: process.env.DATABASE_URL || 'localhost:5432',
options: {
ssl: process.env.DB_SSL === 'true',
},
},
},
};
// 在模块中使用
@Module({
providers: [configProvider],
exports: [configProvider],
})
export class ConfigModule {}
// 在服务中注入
@Injectable()
export class DatabaseService {
constructor(@Inject('CONFIG') private config: any) {}
connect() {
return `Connecting to ${this.config.database.url}`;
}
}
2. 类提供者 (Class Providers)
typescript
// 开发环境配置服务
export class DevelopmentConfigService {
get(key: string): string {
return `dev_${process.env[key]}`;
}
isDevelopment(): boolean {
return true;
}
}
// 生产环境配置服务
export class ProductionConfigService {
get(key: string): string {
return process.env[key];
}
isDevelopment(): boolean {
return false;
}
}
// 类提供者
export const configServiceProvider = {
provide: 'ConfigService',
useClass: process.env.NODE_ENV === 'production'
? ProductionConfigService
: DevelopmentConfigService,
};
// 使用
@Injectable()
export class SomeService {
constructor(@Inject('ConfigService') private configService: any) {}
}
3. 工厂提供者 (Factory Providers)
typescript
import { ConfigService } from './config.service';
// 工厂提供者
export const databaseProvider = {
provide: 'DATABASE_CONNECTION',
useFactory: (configService: ConfigService) => {
const dbConfig = {
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
};
// 创建数据库连接
return createConnection(dbConfig);
},
inject: [ConfigService], // 依赖注入
};
// 复杂工厂提供者
export const loggerProvider = {
provide: 'LoggerService',
useFactory: (configService: ConfigService, request: Request) => {
const options = {
level: configService.get('LOG_LEVEL') || 'info',
format: configService.get('LOG_FORMAT') || 'json',
transports: [
new transports.Console(),
new transports.File({ filename: 'combined.log' }),
],
};
// 为每个请求创建特定的logger实例
return new Logger({
...options,
metadata: {
requestId: request.headers['x-request-id'],
userAgent: request.headers['user-agent'],
},
});
},
inject: [ConfigService, REQUEST], // 注入请求作用域的服务
scope: Scope.REQUEST, // 请求作用域
};
4. 别名提供者 (Alias Providers)
typescript
// 别名提供者 - 为现有提供者创建别名
export const loggerAliasProvider = {
provide: 'CUSTOM_LOGGER',
useExisting: 'LOGGER', // 指向现有的提供者
};
// 在模块中使用
@Module({
providers: [
{
provide: 'LOGGER',
useValue: new ConsoleLogger(),
},
loggerAliasProvider, // 现在 'CUSTOM_LOGGER' 也指向同一个实例
],
})
export class LoggerModule {}
作用域提供者
作用域类型
typescript
import { Injectable, Scope } from '@nestjs/common';
// 单例作用域 (默认) - 整个应用程序共享一个实例
@Injectable()
export class SingletonService {
private count = 0;
increment(): number {
return ++this.count;
}
}
// 请求作用域 - 每个请求创建一个新实例
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
private readonly id = Math.random();
getId(): string {
return this.id.toString();
}
}
// 过渡作用域 - 每次注入都创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
private readonly id = Math.random();
getId(): string {
return this.id.toString();
}
}
请求作用域的实际应用
typescript
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
private readonly requestId: string;
private readonly user: any;
constructor(@Inject(REQUEST) private request: Request) {
this.requestId = this.generateRequestId();
this.user = this.extractUserFromRequest();
}
private generateRequestId(): string {
return this.request.headers['x-request-id'] as string ||
Math.random().toString(36).substr(2, 9);
}
private extractUserFromRequest(): any {
return this.request.user || null;
}
getRequestId(): string {
return this.requestId;
}
getUser(): any {
return this.user;
}
getIp(): string {
return this.request.ip;
}
}
自定义提供者配置
动态提供者
typescript
// 根据环境创建动态提供者
export function createConfigProvider(env: string) {
if (env === 'production') {
return {
provide: 'ConfigService',
useFactory: () => {
return new ProductionConfigService();
},
};
} else {
return {
provide: 'ConfigService',
useClass: DevelopmentConfigService,
};
}
}
// 使用动态提供者
@Module({
providers: [
createConfigProvider(process.env.NODE_ENV),
],
})
export class AppModule {}
条件提供者
typescript
// 条件提供者
export function createCacheProvider(cacheType: string) {
switch (cacheType) {
case 'redis':
return {
provide: 'CACHE_SERVICE',
useFactory: (redisClient: RedisClient) => {
return new RedisCacheService(redisClient);
},
inject: [RedisClient],
};
case 'memory':
return {
provide: 'CACHE_SERVICE',
useClass: MemoryCacheService,
};
default:
return {
provide: 'CACHE_SERVICE',
useClass: NoopCacheService,
};
}
}
提供者生命周期
生命周期钩子
typescript
import {
Injectable,
Scope,
OnModuleInit,
OnModuleDestroy,
OnApplicationBootstrap
} from '@nestjs/common';
@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap {
private connection: any;
async onModuleInit() {
console.log('DatabaseService initialized');
this.connection = await this.connectToDatabase();
}
async onApplicationBootstrap() {
console.log('Application bootstrap complete');
await this.performPostBootstrapTasks();
}
async onModuleDestroy() {
console.log('DatabaseService destroyed');
await this.connection.close();
}
private async connectToDatabase() {
// 连接数据库的逻辑
return { connected: true };
}
private async performPostBootstrapTasks() {
// 启动后执行的任务
}
}
// 请求作用域的生命周期
@Injectable({ scope: Scope.REQUEST })
export class RequestService implements OnModuleInit {
async onModuleInit() {
console.log('RequestService initialized for this request');
}
process() {
return 'Processing request';
}
}
提供者注册
在模块中注册提供者
typescript
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import {
configProvider,
databaseProvider,
loggerProvider
} from './providers';
@Module({
controllers: [CatsController],
providers: [
CatsService, // 类提供者
configProvider, // 值提供者
databaseProvider, // 工厂提供者
loggerProvider, // 工厂提供者(请求作用域)
{
provide: 'CUSTOM_SERVICE',
useFactory: () => {
return new CustomService();
},
},
],
exports: [
CatsService, // 导出服务供其他模块使用
'DATABASE_CONNECTION', // 导出自定义提供者
],
})
export class CatsModule {}
异步提供者
typescript
// 异步提供者
export const asyncDatabaseProvider = {
provide: 'ASYNC_DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
const dbConfig = configService.getDatabaseConfig();
// 异步创建连接
return await createConnection(dbConfig);
},
inject: [ConfigService],
};
// 在模块中使用
@Module({
providers: [asyncDatabaseProvider],
exports: [asyncDatabaseProvider],
})
export class DatabaseModule {}
提供者最佳实践
1. 明确提供者职责
typescript
// 好的提供者设计 - 职责单一
@Injectable()
export class UserService {
constructor(
private databaseService: DatabaseService,
private loggerService: LoggerService,
) {}
async findAll(): Promise<User[]> {
this.loggerService.log('Fetching all users');
return await this.databaseService.findAll('users');
}
async create(userData: CreateUserDto): Promise<User> {
this.loggerService.log('Creating user');
return await this.databaseService.create('users', userData);
}
}
// 避免提供者过于复杂
@Injectable()
export class MassiveService {
// 避免在一个服务中处理过多逻辑
}
2. 合理使用作用域
typescript
// 单例服务 - 适用于无状态或共享状态
@Injectable()
export class CacheService {
private cache = new Map();
get(key: string) {
return this.cache.get(key);
}
set(key: string, value: any) {
this.cache.set(key, value);
}
}
// 请求作用域 - 适用于需要请求特定状态的场景
@Injectable({ scope: Scope.REQUEST })
export class RequestLoggerService {
private startTime: number;
private readonly requestId: string;
constructor(@Inject(REQUEST) private request: Request) {
this.startTime = Date.now();
this.requestId = request.headers['x-request-id'] as string;
}
log(message: string) {
console.log(`[${this.requestId}] ${message}`);
}
getDuration(): number {
return Date.now() - this.startTime;
}
}
3. 类型安全的提供者
typescript
// 使用符号作为令牌以提高类型安全
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');
export const CONFIG_OPTIONS = Symbol('CONFIG_OPTIONS');
@Injectable()
export class UserService {
constructor(
@Inject(DATABASE_CONNECTION) private connection: any,
@Inject(CONFIG_OPTIONS) private options: any,
) {}
}
// 或使用字符串令牌接口
const PROVIDERS = {
DATABASE_CONNECTION: 'DATABASE_CONNECTION',
CONFIG_OPTIONS: 'CONFIG_OPTIONS',
} as const;
@Injectable()
export class UserService {
constructor(
@Inject(PROVIDERS.DATABASE_CONNECTION) private connection: any,
) {}
}
4. 提供者测试
typescript
// 测试提供者
describe('UserService', () => {
let userService: UserService;
let databaseService: DatabaseService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: DatabaseService,
useValue: {
findAll: jest.fn().mockResolvedValue([{ id: 1, name: 'John' }]),
create: jest.fn().mockResolvedValue({ id: 1, name: 'John' }),
},
},
],
}).compile();
userService = module.get<UserService>(UserService);
databaseService = module.get<DatabaseService>(DatabaseService);
});
it('should find all users', async () => {
const users = await userService.findAll();
expect(users).toEqual([{ id: 1, name: 'John' }]);
});
});
高级提供者模式
提供者工厂
typescript
// 提供者工厂模式
export class ProviderFactory {
static createDatabaseProvider(type: 'mysql' | 'postgres' | 'mongodb') {
switch (type) {
case 'mysql':
return {
provide: 'DATABASE_CONNECTION',
useFactory: () => new MysqlConnection(),
};
case 'postgres':
return {
provide: 'DATABASE_CONNECTION',
useFactory: () => new PostgresConnection(),
};
case 'mongodb':
return {
provide: 'DATABASE_CONNECTION',
useFactory: () => new MongoConnection(),
};
}
}
}
// 使用工厂
@Module({
providers: [
ProviderFactory.createDatabaseProvider('postgres'),
],
})
export class DatabaseModule {}
提供者装饰器
typescript
// 自定义提供者装饰器
export function CustomProvider(name: string) {
return Injectable()(
class extends Provider {
static [Symbol.toStringTag] = name;
}
);
}
@CustomProvider('CustomService')
export class MyCustomService {
// 实现
}
通过合理使用NestJS的提供者系统,可以构建高度可测试、可维护和可扩展的应用程序。