什么是提供者?🤔
提供者(Provider)是 NestJS 中一个基础且重要的概念。简单来说,提供者就是用来提供某种功能或数据的类。在 NestJS 中,服务(Service)、仓库(Repository)、工厂(Factory)、助手(Helper)等都可以是提供者。
Express vs NestJS 对比 🆚
让我们看看在传统 Express 和 NestJS 中如何组织业务逻辑:
Express 写法:
javascript
// userService.js
class UserService {
async findAll() {
return [/* 用户数据 */];
}
}
// userController.js
const UserService = require('./userService');
const userService = new UserService(); // 手动实例化
router.get('/users', async (req, res) => {
const users = await userService.findAll();
res.json(users);
});NestJS 写法:
typescript
// users.service.ts
@Injectable() // 👈 声明这是一个可注入的提供者
export class UsersService {
async findAll() {
return [/* 用户数据 */];
}
}
// users.controller.ts
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {} // 👈 自动注入
@Get()
async findAll() {
return this.usersService.findAll();
}
}提供者的类型 🎯
1. 标准提供者
最常见的提供者形式,使用 @Injectable() 装饰器:
typescript
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}2. 自定义提供者 🛠️
值提供者
typescript
// 提供一个简单的值
{
provide: 'API_KEY',
useValue: 'my-api-key'
}类提供者
typescript
{
provide: CatsService,
useClass: CatsService
}工厂提供者
typescript
{
provide: 'ASYNC_CONNECTION',
useFactory: async (configService: ConfigService) => {
const connection = await createConnection(configService.get('DB_CONFIG'));
return connection;
},
inject: [ConfigService] // 👈 注入依赖
}别名提供者
typescript
{
provide: 'ALIAS_SERVICE',
useExisting: ExistingService
}依赖注入详解 💉
1. 构造函数注入
typescript
@Injectable()
export class CatsService {
constructor(
private readonly databaseService: DatabaseService,
@Inject('API_KEY') private apiKey: string, // 👈 注入自定义提供者
@Optional() private readonly configService?: ConfigService // 👈 可选依赖
) {}
}2. 属性注入
typescript
@Injectable()
export class CatsService {
@Inject('API_KEY')
private readonly apiKey: string;
}作用域 🌍
NestJS 提供者默认是单例的,但我们可以改变它的作用域:
typescript
@Injectable({ scope: Scope.REQUEST }) // 👈 请求作用域
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {} // 👈 注入请求对象
}作用域类型:
DEFAULT(单例)REQUEST(每个请求创建新实例)TRANSIENT(每次注入创建新实例)
循环依赖 🔄
有时候会遇到两个类相互依赖的情况:
typescript
@Injectable()
export class CatsService {
constructor(
@Inject(forwardRef(() => DogsService)) // 👈 处理循环依赖
private readonly dogsService: DogsService
) {}
}
@Injectable()
export class DogsService {
constructor(
@Inject(forwardRef(() => CatsService))
private readonly catsService: CatsService
) {}
}实战示例 💡
让我们创建一个完整的用户模块:
typescript
// users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly configService: ConfigService,
private readonly emailService: EmailService
) {}
async create(createUserDto: CreateUserDto) {
const user = this.userRepository.create(createUserDto);
await this.userRepository.save(user);
// 发送欢迎邮件
await this.emailService.sendWelcomeEmail(user.email);
return user;
}
async findAll(query: QueryDto) {
return this.userRepository.find({
skip: (query.page - 1) * query.limit,
take: query.limit
});
}
}
// users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User]), ConfigModule, EmailModule],
providers: [
UsersService,
{
provide: 'USER_CONFIG',
useFactory: (configService: ConfigService) => ({
defaultRole: configService.get('DEFAULT_USER_ROLE'),
maxLoginAttempts: configService.get('MAX_LOGIN_ATTEMPTS')
}),
inject: [ConfigService]
}
],
controllers: [UsersController],
exports: [UsersService]
})
export class UsersModule {}最佳实践 ✨
1. 保持服务的单一职责
typescript
// ❌ 不好的做法
@Injectable()
export class UserService {
async createUser() {
// 创建用户
// 发送邮件
// 生成PDF报告
// 更新统计数据
}
}
// ✅ 好的做法
@Injectable()
export class UserService {
constructor(
private readonly emailService: EmailService,
private readonly reportService: ReportService,
private readonly statsService: StatsService
) {}
async createUser() {
const user = await this.saveUser();
await this.emailService.sendWelcomeEmail(user);
await this.reportService.generateReport(user);
await this.statsService.updateStats(user);
return user;
}
}2. 使用接口和抽象类
typescript
// storage.interface.ts
export interface IStorageService {
upload(file: Buffer): Promise<string>;
delete(path: string): Promise<void>;
}
// storage.service.ts
@Injectable()
export class S3StorageService implements IStorageService {
async upload(file: Buffer): Promise<string> {
// 实现S3上传逻辑
}
async delete(path: string): Promise<void> {
// 实现S3删除逻辑
}
}
// 在模块中配置
{
provide: 'STORAGE_SERVICE',
useClass: S3StorageService
}3. 适当使用异步提供者
typescript
{
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
const connection = await createConnection({
host: config.get('DB_HOST'),
port: config.get('DB_PORT')
});
// 等待连接就绪
await connection.connect();
return connection;
},
inject: [ConfigService]
}总结 📝
通过这篇文章,我们学习了:
- 提供者的概念和类型
- 依赖注入的不同方式
- 提供者的作用域
- 如何处理循环依赖
- 实际开发中的最佳实践
提供者和依赖注入是 NestJS 的核心特性,掌握好它们可以帮助我们写出更加模块化和可维护的代码。
下一篇文章,我们将探讨 NestJS 的模块(Module)系统。