簡單來說,Service 就是在復雜業(yè)務場景下用于做業(yè)務邏輯封裝的一個抽象層,提供這個抽象有以下幾個好處:
- 保持 Controller 中的邏輯更加簡潔。
- 保持業(yè)務邏輯的獨立性,抽象出來的 Service 可以被多個 Controller 重復調用。
- 將邏輯和展現分離,更容易編寫測試用例,測試用例的編寫具體可以查看這里。
使用場景
- 復雜數據的處理,比如要展現的信息需要從數據庫獲取,還要經過一定的規(guī)則計算,才能返回用戶顯示?;蛘哂嬎阃瓿珊螅碌綌祿?。
- 第三方服務的調用,比如 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;
|
屬性
每一次用戶請求,框架都會實例化對應的 Service 實例,由于它繼承于 egg.Service,故擁有下列屬性方便我們進行開發(fā):
- this.ctx: 當前請求的上下文 Context 對象的實例,通過它我們可以拿到框架封裝好的處理當前請求的各種便捷屬性和方法。
- this.app: 當前應用 Application 對象的實例,通過它我們可以拿到框架提供的全局對象和方法。
- this.service:應用定義的 Service,通過它我們可以訪問到其他業(yè)務層,等價于 this.ctx.service 。
- this.config:應用運行時的配置項。
- this.logger:logger 對象,上面有四個方法(debug,info,warn,error),分別代表打印四個不同級別的日志,使用方法和效果與 context logger 中介紹的一樣,但是通過這個 logger 對象記錄的日志,在日志前面會加上打印該日志的文件路徑,以便快速定位日志打印位置。
Service ctx 詳解
為了可以獲取用戶請求的鏈路,我們在 Service 初始化中,注入了請求上下文, 用戶在方法中可以直接通過 this.ctx 來獲取上下文相關信息。關于上下文的具體詳解可以參看 Context, 有了 ctx 我們可以拿到框架給我們封裝的各種便捷屬性和方法。比如我們可以用:
- this.ctx.curl 發(fā)起網絡調用。
- this.ctx.service.otherService 調用其他 Service。
- this.ctx.db 發(fā)起數據庫調用等, 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 獲取到當前請求的上下文。
使用 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 { // 默認不需要提供構造函數。 // constructor(ctx) { // super(ctx); 如果需要在構造函數做一些處理,一定要有這句話,才能保證后面 `this.ctx`的使用。 // // 就可以直接通過 this.ctx 獲取 ctx 了 // // 還可以直接通過 this.app 獲取 app 了 // } async find(uid) { // 假如 我們拿到用戶 id 從數據庫獲取用戶詳細信息 const user = await this.ctx.db.query('select * from user where uid = ?', uid);
// 假定這里還有一些復雜的計算,然后返回需要的信息。 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 |
更多建議: