Skip to content

什么是提供者?🤔 ​

提供者(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]
}

总结 📝 ​

通过这篇文章,我们学习了:

  1. 提供者的概念和类型
  2. 依赖注入的不同方式
  3. 提供者的作用域
  4. 如何处理循环依赖
  5. 实际开发中的最佳实践

提供者和依赖注入是 NestJS 的核心特性,掌握好它们可以帮助我们写出更加模块化和可维护的代码。

下一篇文章,我们将探讨 NestJS 的模块(Module)系统。

相关资源 📚 ​