3、Nest.js 中的依赖注入与提供者

什么是依赖注入?

依赖注入(Dependency Injection,简称DI) 是实现 控制反转(Inversion of Control,缩写为IoC) 的一种常见方式。

那什么是控制反转呢?

这里摘抄一下百科上的解释,控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象。

现在说人话:
假设你是一个想开公司的富二代,开公司首先需要一间办公室。那么你不用自己去买,你只需要在你的清单列表上写上办公室这么一项,OK! 你老爸已经派人给你安排好了办公室,这间办公室长什么样?多大?在哪里?是租的?还是买的?你根本不知道,你也不需要知道。 现在你又在清单上写了需要80台办公电脑,你老爸又给你安排好了80台 Alienware, 你自己并不需要关心这些电脑是什么配置,买什么样的CPU性价比更高,只要他们是能办公的电脑就行了。

那么你的老爸就是所谓的 IoC 容器,你在编写 Company 这个 class 的时候,你内部用到的 Office 、Computers
对象不需要你自己导入和实例化,你只需要在 Company 这个类的 Constructor (构造函数) 中声明你需要的对象,IoC 容器会帮你把所依赖的对象实例注入进去。

代码可能类似于下面这样:

import { IOffice } from './interfaces/IOffice.interface';
import { IComputers } from './interfaces/IComputers.interface';

export class Company {
  constructor(
    private readonly office: IOffice,
    private readonly computers: IComputers[]
  ) {}
}

这里有必要解释一下 readonly 关键字,它表示被修饰的对象只能在声明的时候或构造函数中初始化。

Nest 中的依赖注入

Nest 就是建立在依赖注入这种设计模式之上的,所以它在框架内部封装了一个IoC容器来管理所有的依赖关系。

在 MVC 设计模式中, Controller 只负责对请求的分发,并不处理实际的业务逻辑。所以我们的 UsersController 不应该自己处理对 用户 的增、删、改、查。 需要把这些工作交给 Service 层。

按照标准的 面向接口编程 我们在 UserController 构造函数里应该声明实现 IUserService 接口的 UserService 对象,如下:

src/users/interfaces/user-service.interface.ts

import { User } from './user.interface';

export interface IUserService {

   findAll(): Promise<User[]>;

   findOne(id: number): Promise<User>;

   create();

   edit();

   remove();

}

src/users/users.controller.ts

import { Controller, Param, Get, Post, Delete, Put } from '@nestjs/common';
import { User } from './interfaces/user.interface';
import { IUsersService } from './interfaces/user-service.interface';

@Controller('users')
export class UsersController {

    constructor(private readonly usersService: IUsersService) {

    }

    @Get()
    async findAll(): Promise<User[]> {

        return await this.usersService.findAll();
    }

    @Get(':id')
    async findOne(@Param() params): Promise<User> {
        
        return await this.usersService.findOne(params.id);
    }

    @Post()
    async create() {
        return await this.usersService.create();
    }

    @Put()
    async edit() {
        return await this.usersService.edit();
    }

    @Delete()
    async remove() {
        return await this.usersService.remove();
    }
}

但是 TypeScript 只是一门转译语言, 最终生成并执行的是 JavaScript, 所以 TypeScript 的 Interface 会在转译成 Javascript 后去除,Nest 无法引用这些接口,在Nest中我们需要为IoC容器指定 提供者

提供者(Provider)

一个提供者就是一个可以被IoC容器注入依赖关系的对象。

现在我们来新建一个提供者 UserService:

$ nest g s users/services/users

CREATE /src/users/services/users.service.spec.ts (449 bytes)
CREATE /src/users/services/users.service.ts (89 bytes)
UPDATE /src/app.module.ts (858 bytes)

生成的代码如下:

src/users/services/users.service.ts

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

@Injectable()
export class UsersService  { }

在Nest中不仅仅只有 Service 能作为提供者,repository、 factory、 helper 等等,都可以作为提供者,也可以相互依赖。
所谓 提供者 不过就是一个简单的 JavaScript 类, 然后使用 @Injectable 装饰器修饰。

现在我们让 UserService 实现 IUserService 接口,并给框架指定提供者:

src/users/services/user.service.ts

import { Injectable } from '@nestjs/common';
import { User } from '../interfaces/user.interface';
import { IUserService } from '../interfaces/user-service.interface';

@Injectable()
export class UsersService implements IUserService {
    async findAll(): Promise<User[]> {
        return [];
    }

    async findOne(id: number): Promise<User> {
        return {
            id,
            name: '小明',
            age: 18
        };
    }

    async create() {
        
    }

    async edit() {

    }

    async remove() {

    }
}

目前我们在 app.module.ts 中指定:

src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from 'app.controller';
import { AppService } from 'app.service';
import { UsersController } from 'users/users.controller';
import { UsersService } from './users/services/users.service';

@Module({
  imports: [],
  controllers: [AppController, UsersController],
  providers: [AppService, UsersService],
})
export class AppModule { }

在 @Module 装饰器中我们给 providers 数组新增一个 UsersService 提供者

如果你一直跟着前面的教程做,现在访问 http://127.0.0.1:3000/users/33,会得到下面这样预期的结果:

{"id":"33","name":"小明","age":18}

得益于依赖注入设计模式,假设我们现在想要更换 UserService 的实现, 我们只需要专注于修改这个提供者,然后对这个提供者进行单元测试。
如果能够实现标准的面向接口编程一切将会变得更好,我们的程序耦合度将会更低。

上一篇:2、Nest.js 创建基本的REST控制器
下一篇:4、Nest.js 中的模块化设计