Egg 服務(wù)(Service)

2020-02-06 14:10 更新

簡(jiǎn)單來說,Service 就是在復(fù)雜業(yè)務(wù)場(chǎng)景下用于做業(yè)務(wù)邏輯封裝的一個(gè)抽象層,提供這個(gè)抽象有以下幾個(gè)好處:

  • 保持 Controller 中的邏輯更加簡(jiǎn)潔。
  • 保持業(yè)務(wù)邏輯的獨(dú)立性,抽象出來的 Service 可以被多個(gè) Controller 重復(fù)調(diào)用。
  • 將邏輯和展現(xiàn)分離,更容易編寫測(cè)試用例,測(cè)試用例的編寫具體可以查看這里。

使用場(chǎng)景

  • 復(fù)雜數(shù)據(jù)的處理,比如要展現(xiàn)的信息需要從數(shù)據(jù)庫(kù)獲取,還要經(jīng)過一定的規(guī)則計(jì)算,才能返回用戶顯示。或者計(jì)算完成后,更新到數(shù)據(jù)庫(kù)。
  • 第三方服務(wù)的調(diào)用,比如 GitHub 信息獲取等。

定義 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;

屬性

每一次用戶請(qǐng)求,框架都會(huì)實(shí)例化對(duì)應(yīng)的 Service 實(shí)例,由于它繼承于 egg.Service,故擁有下列屬性方便我們進(jìn)行開發(fā):

  • this.ctx: 當(dāng)前請(qǐng)求的上下文 Context 對(duì)象的實(shí)例,通過它我們可以拿到框架封裝好的處理當(dāng)前請(qǐng)求的各種便捷屬性和方法。
  • this.app: 當(dāng)前應(yīng)用 Application 對(duì)象的實(shí)例,通過它我們可以拿到框架提供的全局對(duì)象和方法。
  • this.service:應(yīng)用定義的 Service,通過它我們可以訪問到其他業(yè)務(wù)層,等價(jià)于 this.ctx.service 。
  • this.config:應(yīng)用運(yùn)行時(shí)的配置項(xiàng)
  • this.logger:logger 對(duì)象,上面有四個(gè)方法(debug,info,warn,error),分別代表打印四個(gè)不同級(jí)別的日志,使用方法和效果與 context logger 中介紹的一樣,但是通過這個(gè) logger 對(duì)象記錄的日志,在日志前面會(huì)加上打印該日志的文件路徑,以便快速定位日志打印位置。

Service ctx 詳解

為了可以獲取用戶請(qǐng)求的鏈路,我們?cè)?Service 初始化中,注入了請(qǐng)求上下文, 用戶在方法中可以直接通過 this.ctx 來獲取上下文相關(guān)信息。關(guān)于上下文的具體詳解可以參看 Context, 有了 ctx 我們可以拿到框架給我們封裝的各種便捷屬性和方法。比如我們可以用:

  • this.ctx.curl 發(fā)起網(wǎng)絡(luò)調(diào)用。
  • this.ctx.service.otherService 調(diào)用其他 Service。
  • this.ctx.db 發(fā)起數(shù)據(jù)庫(kù)調(diào)用等, db 可能是其他插件提前掛載到 app 上的模塊。

注意事項(xiàng)

  • Service 文件必須放在 app/service 目錄,可以支持多級(jí)目錄,訪問的時(shí)候可以通過目錄名級(jí)聯(lián)訪問。app/service/biz/user.js => ctx.service.biz.userapp/service/sync_user.js => ctx.service.syncUserapp/service/HackerNews.js => ctx.service.hackerNews
  • 一個(gè) Service 文件只能包含一個(gè)類, 這個(gè)類需要通過 module.exports 的方式返回。
  • Service 需要通過 Class 的方式定義,父類必須是 egg.Service。
  • Service 不是單例,是 請(qǐng)求級(jí)別 的對(duì)象,框架在每次請(qǐng)求中首次訪問 ctx.service.xx 時(shí)延遲實(shí)例化,所以 Service 中可以通過 this.ctx 獲取到當(dāng)前請(qǐng)求的上下文。

使用 Service

下面就通過一個(gè)完整的例子,看看怎么使用 Service。

// 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;

// app/service/user.js
const Service = require('egg').Service;
class UserService extends Service {
// 默認(rèn)不需要提供構(gòu)造函數(shù)。
// constructor(ctx) {
// super(ctx); 如果需要在構(gòu)造函數(shù)做一些處理,一定要有這句話,才能保證后面 `this.ctx`的使用。
// // 就可以直接通過 this.ctx 獲取 ctx 了
// // 還可以直接通過 this.app 獲取 app 了
// }
async find(uid) {
// 假如 我們拿到用戶 id 從數(shù)據(jù)庫(kù)獲取用戶詳細(xì)信息
const user = await this.ctx.db.query('select * from user where uid = ?', uid);

// 假定這里還有一些復(fù)雜的計(jì)算,然后返回需要的信息。
const picture = await this.getPicture(uid);

return {
name: user.user_name,
age: user.age,
picture,
};
}

async getPicture(uid) {
const result = await this.ctx.curl(`http://photoserver/uid=${uid}`, { dataType: 'json' });
return result.data;
}
}
module.exports = UserService;

// curl http://127.0.0.1:7001/user/1234


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)