NestJS提供者 (Providers)

学习目标

  • 理解提供者在NestJS中的核心地位
  • 掌握依赖注入的基本原理
  • 学会创建和使用服务(Service)
  • 理解不同作用域(Scope)的区别和应用场景

核心知识点

1. 提供者概念

提供者是NestJS中一个核心概念,它可以被注入到其他组件中。几乎NestJS中的所有东西都可以被视为提供者:服务(Services)、仓库(Repositories)、工厂(Factories)、助手(Helpers)等。

提供者的主要特点:

  • 它们可以通过构造函数注入依赖
  • 它们可以被模块组织和管理
  • 它们可以有不同的作用域
  • 它们是NestJS实现控制反转(IoC)和依赖注入(DI)的基础

2. 依赖注入原理

依赖注入是一种设计模式,它允许我们将对象的创建和依赖关系的管理从代码中分离出来。在NestJS中:

  • 依赖关系在构造函数中声明
  • NestJS的依赖注入容器负责创建和注入依赖项
  • 这使得代码更加模块化、可测试和可维护

3. 创建服务

服务是最常见的提供者类型,用于封装业务逻辑。创建一个服务的步骤:

  1. 创建一个类
  2. 使用@Injectable()装饰器标记它
  3. 在模块的providers数组中注册它
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  private readonly cats = [];

  create(cat) {
    this.cats.push(cat);
    return cat;
  }

  findAll() {
    return this.cats;
  }
}

4. 注入服务

将服务注入到控制器或其他服务中:

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  create(@Body() cat) {
    return this.catsService.create(cat);
  }

  @Get()
  findAll() {
    return this.catsService.findAll();
  }
}

5. 模块注册

在模块中注册提供者:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

6. 作用域

NestJS提供三种作用域:

  1. 默认作用域(单例):每个提供者默认是单例的,在应用程序生命周期内只创建一次
  2. 请求作用域:每个请求创建一个新的提供者实例
  3. 事务作用域:在特定的事务上下文中创建实例

使用@Injectable()装饰器设置作用域:

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class CatsService {
  // ...
}

7. 自定义提供者

除了使用@Injectable()装饰器创建提供者外,NestJS还支持多种自定义提供者模式:

  • 值提供者:直接提供一个值
  • 类提供者:使用useClass指定实现类
  • 工厂提供者:使用useFactory创建提供者
  • 别名提供者:使用useExisting创建别名
// 值提供者
const configProvider = {
  provide: 'CONFIG',
  useValue: {
    apiKey: 'secret_key',
    apiUrl: 'https://api.example.com',
  },
};

// 类提供者
const loggerProvider = {
  provide: LoggerService,
  useClass: process.env.NODE_ENV === 'production' ? ProductionLoggerService : DevelopmentLoggerService,
};

// 工厂提供者
const databaseProvider = {
  provide: 'DATABASE_CONNECTION',
  useFactory: async (configService: ConfigService) => {
    const connection = await createConnection({
      type: 'mysql',
      host: configService.get('DB_HOST'),
      port: configService.get('DB_PORT'),
    });
    return connection;
  },
  inject: [ConfigService],
};

8. 注入自定义提供者

使用@Inject()装饰器注入自定义提供者:

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class CatsService {
  constructor(
    @Inject('CONFIG') private config: any,
    @Inject('DATABASE_CONNECTION') private connection: any,
  ) {}
  
  // ...
}

9. 循环依赖

当两个或多个模块相互依赖时,会产生循环依赖。NestJS提供了两种解决方案:

  1. 前向引用(Forward Reference):使用forwardRef()函数
  2. 模块引用(Module Reference):使用ModuleRef动态获取实例

使用前向引用:

import { Module, forwardRef } from '@nestjs/common';
import { CatsModule } from './cats.module';
import { DogsModule } from './dogs.module';

@Module({
  imports: [forwardRef(() => DogsModule)],
})
export class CatsModule {}

@Module({
  imports: [forwardRef(() => CatsModule)],
})
export class DogsModule {}

实践案例分析

案例:用户管理系统

需求分析

我们需要创建一个用户管理系统,包含以下功能:

  • 用户注册
  • 用户登录
  • 获取用户信息
  • 更新用户信息

实现步骤

  1. 创建用户服务
  2. 创建认证服务
  3. 在控制器中注入和使用这些服务
  4. 配置模块

代码实现

1. 创建用户服务
// users.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private readonly users = [
    { id: 1, name: '张三', email: 'zhangsan@example.com', password: 'password123' },
    { id: 2, name: '李四', email: 'lisi@example.com', password: 'password456' },
  ];

  findOne(email: string) {
    return this.users.find(user => user.email === email);
  }

  findById(id: number) {
    return this.users.find(user => user.id === id);
  }

  create(user) {
    const newUser = {
      id: this.users.length + 1,
      ...user,
    };
    this.users.push(newUser);
    return newUser;
  }

  update(id: number, userData) {
    const index = this.users.findIndex(user => user.id === id);
    if (index === -1) {
      return null;
    }
    
    this.users[index] = {
      ...this.users[index],
      ...userData,
    };
    
    return this.users[index];
  }
}
2. 创建认证服务
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { UsersService } from './users.service';

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  async validateUser(email: string, password: string) {
    const user = this.usersService.findOne(email);
    if (user && user.password === password) {
      // 在实际应用中,应该返回不包含密码的用户对象
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user) {
    // 在实际应用中,应该生成JWT令牌
    return {
      access_token: 'mock_token',
      user,
    };
  }
}
3. 创建用户控制器
// users.controller.ts
import { Controller, Get, Post, Put, Param, Body, Request } from '@nestjs/common';
import { UsersService } from './users.service';
import { AuthService } from './auth.service';

@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly authService: AuthService,
  ) {}

  @Post('register')
  async register(@Body() userData) {
    return this.usersService.create(userData);
  }

  @Post('login')
  async login(@Body() loginData) {
    const { email, password } = loginData;
    const user = await this.authService.validateUser(email, password);
    if (!user) {
      return { message: 'Invalid credentials' };
    }
    return this.authService.login(user);
  }

  @Get(':id')
  async getUser(@Param('id') id: number) {
    return this.usersService.findById(id);
  }

  @Put(':id')
  async updateUser(@Param('id') id: number, @Body() userData) {
    return this.usersService.update(id, userData);
  }
}
4. 配置模块
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { AuthService } from './auth.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService, AuthService],
  exports: [UsersService, AuthService], // 导出以便其他模块使用
})
export class UsersModule {}

代码解析

  1. 服务创建

    • 使用@Injectable()装饰器创建了UsersServiceAuthService
    • UsersService负责用户数据的CRUD操作
    • AuthService负责认证相关的逻辑
  2. 依赖注入

    • AuthService注入了UsersService来验证用户
    • UsersController注入了UsersServiceAuthService来处理请求
  3. 模块配置

    • UsersModule中注册了控制器和提供者
    • 使用exports数组导出服务,以便其他模块可以使用
  4. 作用域

    • 所有服务都使用默认的单例作用域

测试结果

请求方法 请求路径 描述 预期响应
POST /users/register 注册新用户 {"id":3,"name":"王五","email":"wangwu@example.com","password":"password789"}
POST /users/login 用户登录 {"access_token":"mock_token","user":{"id":1,"name":"张三","email":"zhangsan@example.com"}}
GET /users/1 获取用户信息 {"id":1,"name":"张三","email":"zhangsan@example.com","password":"password123"}
PUT /users/1 更新用户信息 {"id":1,"name":"张三更新","email":"zhangsan@example.com","password":"password123"}

互动思考问题

  1. 思考:依赖注入的主要优势是什么?在大型应用中,它如何帮助我们管理代码?

  2. 讨论:单例作用域和请求作用域的区别是什么?在什么情况下应该使用请求作用域?

  3. 实践:尝试创建一个博客系统,包含文章服务、评论服务和用户服务,实现它们之间的依赖注入。

  4. 挑战:如何处理循环依赖问题?尝试创建两个相互依赖的服务,并使用前向引用解决循环依赖。

  5. 扩展:了解NestJS的ModuleRefInjector,思考它们如何用于动态获取和管理依赖。

小结

本集我们学习了NestJS提供者的核心概念和依赖注入系统,包括:

  • 提供者的基本概念和类型
  • 依赖注入的原理和实现
  • 服务的创建和使用方法
  • 不同作用域的区别和应用
  • 自定义提供者的创建和注入
  • 循环依赖的解决方案

通过实践案例,我们创建了一个完整的用户管理系统,展示了如何使用服务和依赖注入来组织和管理应用的业务逻辑。提供者是NestJS应用的核心构建块,它们使得代码更加模块化、可测试和可维护。

在下一集中,我们将学习NestJS的模块(Modules),了解如何使用模块来组织和管理应用的结构,这将帮助我们构建更加大型和复杂的应用。

« 上一篇 NestJS控制器 (Controllers) 下一篇 » NestJS模块 (Modules)