簡單來說,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 |
更多建議: