Egg 服務(wù)(Service)

2020-02-06 14:10 更新

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

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

使用場景

  • 復(fù)雜數(shù)據(jù)的處理,比如要展現(xiàn)的信息需要從數(shù)據(jù)庫獲取,還要經(jīng)過一定的規(guī)則計算,才能返回用戶顯示。或者計算完成后,更新到數(shù)據(jù)庫。
  • 第三方服務(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;

屬性

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

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

Service ctx 詳解

為了可以獲取用戶請求的鏈路,我們在 Service 初始化中,注入了請求上下文, 用戶在方法中可以直接通過 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ù)庫調(diào)用等, db 可能是其他插件提前掛載到 app 上的模塊。

注意事項

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

使用 Service

下面就通過一個完整的例子,看看怎么使用 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ù)庫獲取用戶詳細(xì)信息
const user = await this.ctx.db.query('select * from user where uid = ?', uid);

// 假定這里還有一些復(fù)雜的計算,然后返回需要的信息。
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)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號