一、midway service 与 egg service 的区别

midway 的 serivce 与 egg 的 service 没有什么区别,只不过 midway 通过 IOC 的方式更加方便组织 service

egg 中的 service

在 egg 中,我们定义并且实现一个 service 代码可能如下:

// app/service/user.js
const Service = require('egg').Service;

class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.db.query('select * from user where uid = ?', uid);
    return user;
  }
}

module.exports = UserService;

如果要使用一个 service,不需要特殊的引入操作,直接通过上下文信息 this.ctx.service.xxx 调用即可。

// app/router.js
module.exports = app => {
  app.router.get('/user/:id', app.controller.user.info);
};

// app/controller/user.js
const Controller = require('egg').Controller;
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    const userId = ctx.params.id;
    const userInfo = await ctx.service.user.find(userId);
    ctx.body = userInfo;
  }
}
module.exports = UserController;

midway 中的 service

midway 中, service 通过 IOC 控制反转的方式定义和使用 service,并且在启动的时候做了扫描和注入的工作,声明实现一个 service 的时候不再需要继承 egg.Service

如果要定义一个 service 只需要通过 @provide 注解声明即可

import { provide } from "midway";
import { IHttpService } from '../interfaces/http';

@provide('httpService')
export class HttpService implements IHttpService {
    // 模拟 HTTP GET 
    async get() {
        return Promise.resolve({
            res: true,
            message: 'message'
        });
    }
}

当然上面的代码中,IHttpService 只是我自己定义的一个接口而已,不是关键信息。

我们可以看到 @provide('httpService') 已经通过注解声明了名称为 httpService 的 service,注意这里并没有显示的去继承 Service

声明实现了一个 service 之后,就可以在应用内通过 @inject('serviceName') 的方式引入 service:

    @inject('httpService')
    http: any

    @get('/http/test')
    async httptest(ctx: Context) {
        this.ctx.body = await this.http.get(); 
    }

效果:

68770-83c61eqhpas.png

二、midway 实现 service 的相互调用

有的时候我们可能需要在某个 service 中调用另外一个 service,IoC 的实现方式,能够方便我们在实现了一个 service 之后,在其他地方比如plugin、extend、service、controller 中通过 @inject 引入 service

比如现在有一个 componentService 需要调用上面声明的 httpService:

@provide('componentService')
export class ComponentService implements IComponentService {

    @inject('httpService')
    http: any

    async create(): Promise<IComponentDTO> {
        const res = await this.http.get();
        return Promise.resolve(res);
    }
}

ComponentService 中,实现了接口 IComponentService,并且通过 @inject('httpService') 注入了 httpService,使得我们能够在代码中直接调用 this.http.get()

service 的相互调用在某种程度上使得项目的协作开发与依赖拆分更加方便,项目的维护成本也降低很多。

三、midway 中实现多个 service 合并输出

有些场景下,一个 service 可能实现的代码太多了,一个文件可能放不下,也不利于维护,代码量太大,旺旺这种场景下我们会考虑拆分 service。

比如我有一个 service,需要操作 component 和 page,而 component 和 page 每个服务本身有很多方法和代码,我拆分了两个文件并且放在一个文件夹中,目录如下组织:

13745-pqa4egica9.png

实现的 page.ts 如下:

import { provide } from 'midway';

@provide('pageService')
export class PageService {
    async getById(id: string | number): Promise<any> {
        return new Promise((resolve, reject) => {
            resolve({ pageId: id, name: 'name' })
        });
    }
}

实现的 componentService 如下:

import { provide, inject } from 'midway';
import { IComponentDTO, IComponentService } from '../../interfaces/component';
// import { IHttpService } from '../interfaces/http';


@provide('componentService')
export class ComponentService implements IComponentService {

    @inject('httpService')
    http: any

    async create(): Promise<IComponentDTO> {
        const res = await this.http.get();
        return Promise.resolve(res);
    }
}

最终目的是将 componentService 和 pageService 合并到一个 service,也就是在 controller 中使用的时候,我只需要 inject 一个 service。

这个时候 IoC 的优点又体现出来,我们只需要在要对外暴露出来的文件中,将 componentService 和 pageService 注入进来即可,因为当注入进来之后,当前 Service 实例上会自动挂载 this.componentthis.page,并且会对外抛出来:

import { provide, inject } from 'midway';

@provide('testService')
export class TestService {
    @inject('componentService')
    component: any

    @inject('pageService')
    page: any
}

如果需要在 controller 中使用这个 service,我们只需要 @inject testService 这个 service 即可,然后调用代码中,通过如下方式调用:

    @inject('testService')
    test: any


    @get('/test/test')
    async testtest() {
        const id: number | string = 123;
        const demoComponent: IComponentDTO = {
            id: '123',
            latestVersion: '1.2.2',
            name: 'test-name'
        };
        const componentInfo = await this.test.component.update(id, demoComponent);
        this.ctx.body = { success: true, message: '', data: componentInfo };
    }

最终效果和直接 inject componentService 是一样的:

34116-iml6c1nbrr.png

四、不要尝试嵌套 inject 注入 service

比如 serviceA inject 了 serviceB,serviceB 又 inject 了 serviceA

千万不要做这样的事情

千万不要做这样的事情

千万不要做这样的事情

死循环会直接崩掉。