在业务中,只有控制器(Controller)的代码是不够的,一般来说会有一些业务逻辑被抽象到一个特定的逻辑单元中,我们一般称为服务(Service)。

image.png

提供这个抽象有以下几个好处:

  • 保持 Controller 中的逻辑更加简洁。
  • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
  • 将逻辑和展现分离,更容易编写测试用例。

# 创建服务


在 Midway 中,普通的服务就是一个 Class,比如我们之前创建了一个接受 user 请求的 Controller,这些我们来新增一个处理这些数据的服务。

对于服务的文件,我们一般会存放到 src/service 目录中。我们来添加一个 user 服务。

➜  my_midway_app tree
.
├── src
│   ├── controller
│   │   ├── user.ts
│   │   └── home.ts
│   ├── interface.ts
│   └── service
│       └── user.ts
├── test  
├── package.json  
└── tsconfig.json

内容为:

// src/service/user.ts
import { Provide } from '@midwayjs/decorator';

@Provide()
export class UserService {

  async getUser(id: number) {
    return {
      id,
      name: 'Harry',
      age: 18,
    };
  }
}

除了一个 @Provide 装饰器外,整个服务的结构和普通的 Class 一模一样,这样就行了。

之前我们还增加了一个 User 定义,这里也可以直接使用。

import { Provide } from '@midwayjs/decorator';
import { User } from '../interface';

@Provide()
export class UserService {

  async getUser(id: number): Promise<User> {
    return {
      id,
      name: 'Harry',
      age: 18',
    };
  }
}

# 使用服务


在 Controller 处,我们需要来调用这个服务。传统的代码写法,我们需要初始化这个 Class(new),然后将实例放在需要调用的地方。在 Midway 中,你不需要这么做,只需要编写我们提供的** "依赖注入" **的代码写法。

import { Inject, Controller, Post, Provide, Query } from '@midwayjs/decorator';
import { UserService } from '../service/user';

@Provide()
@Controller('/api/user')
export class APIController {

  @Inject()
  userService: UserService;

  @Get('/')
  async getUser(@Query('id') uid) {
    const user = await this.userService.getUser(uid);
    return {success: true, message: 'OK', data: user};
  }
}


使用服务的过程分为几部分:

  • 1、使用 @Provide 装饰器暴露你的服务
  • 2、在调用的代码处,使用 @Inject 装饰器注入你的服务
  • 3、调用注入服务,执行对应的方法


Midway 的核心 “依赖注入” 容器会自动关联你的控制器(Controller) 和服务(Service),在运行过程中会自动初始化所有的代码,你无需手动初始化这些 Class。

TIP

这里导入 UserService 是为了增加类型提示,实际运行过程中并不需要。

# 注入行为描述


看到这里,你应该明白为什么控制器(Controller) 和服务(Service)上都有一个 @Provide 装饰器。不仅如此,之后的大部分代码中,你都会发现这个装饰器。

@Provide 装饰器的作用:

  • 1、这个 Class,被依赖注入容器托管,会自动被实例化(new)
  • 2、这个 Class,可以被其他在容器中的 Class 注入


而对应的 @Inject 装饰器,作用为:

  • 1、在依赖注入容器中,找到对应的属性名,并赋值为对应的实例化对象

TIP

@Inject 的类中,必须有 @Provide 才会生效。

# 注入参数


@Provide 和 @Inject 装饰器是有参数的,并且他们是成对出现。

这个参数叫做 依赖注入标识符,为了方便理解,我们这里先用 key 代替。

默认情况下:

  • 1、 @Provide 取 **类名的驼峰字符串 **作为 key
  • 2、 @Inject 根据 规则 获取 key


规则如下:

  • 1、如果装饰器包含参数,则以 **参数字符串 **作为 key
  • 2、如果没有参数,标注的 TS 类型为 Class,则将类 @Provide 的 key 作为 key
  • 3、如果没有参数,标注的 TS 类型为非 Class,则将 属性名 作为 key


两者相互一致即可关联。

export interface IService {
}

// service
@Provide()													// <------ 这里暴露的 key 是 userService
export class UserService 
  implements IService {
  //...
}

// controller
@Provide()
@Controller('/api/user')
export class APIController {

  @Inject('userService')         		// <------ 这里注入的 key 是 userService
  userService1: UserService;
    
  @Inject()
  userService2: UserService;				// <------ 这里的类型是 Class,注入的 key 是 userService

    
  @Inject()
  userService: IService;						// <------ 这里的类型是 Interface,注入的 key 是 userService

  //...
}



我们可以修改暴露给依赖注入容器的 key,同时,注入的地方也要相应修改。

// service
@Provide('bbbService')               // <------ 这里暴露的 key 是 bbbService
export class UserService {
  //...
}
               
// controller
@Provide()
@Controller('/api/user')
export class APIController {

  @Inject('bbbService')              // <------ 这里注入的 key 是 bbbService
  userService: UserService;
	
  //...
}


这样的组合之后会用到很多地方,请务必记住这个用法