一、GraphQL
最近服務端的同事分享了GraphQL,他分享的目的就是要把我們與他們的資料庫隔離,這麼做我們也求之不得。
我們組目前維護著一個後臺管理系統,會直接讀取資料庫中的表,如果能隔離的話,就不需要寫Model檔案了。
後面再進一步瞭解後,明白了服務端推這個GraphQL的用意,其實就是讓我們自己去維護GraphQL服務,包括客戶端也去自己維護。
那這和我直接讀取資料庫做路由,區別不是很大了,無法解決當前我們的痛點,而且我在前端頁面中還需要制定資料結構,比之前還多了一步。
況且如果需要快取的話,還不能直接呼叫GraphQL的介面。如果我們人員充沛的話,這麼分一點問題都沒有,但是現在人員非常緊張。
我們還要花大精力做資料整合和處理,而前端常規的工作諸如效能優化、頁面互動、元件化、工程化等都沒時間深入研究。基於此,還得另闢蹊徑。
二、通用介面
由於後臺管理系統大部分的操作都是增刪改查(資料庫基於MySQL,ORM基於Sequelize),所以可以抽象出一套這類的通用介面,從而就能避免在 Router 和 Service 兩層中新增不必要的檔案。
api/get:讀取一條資料(單表查詢)
api/gets:讀取多條資料(單表查詢)
api/head:讀取聚合資料,例如count()、sum()、max()和min()
api/post:提交資料,用於增加記錄api/put:更新資料
這套介面的研發受到了 APIJSON 這套開源專案的啟發。
資料庫表都是單表查詢,不支援聯表,若要聯表則單獨建立介面。查詢條件的語法直接參照 Sequelize,沒有做單獨的語法編譯。
由於介面的引數是一個JSON格式的物件,因此全部採用 post 的請求方式(Content-Type: application/json)。
以 api/get 為例,基於KOA框架,在 Service 層的方法是:
/**
* 資料庫查詢一條記錄
*/
async getOne(tableName, where={}) {
return this.models[tableName].findOne({
where,
raw: true
});
}
在 Router 層的方法是:
/**
* 讀取一條記錄
* {
* TableName : { 查詢條件 }
* }
* TableName是Model檔案的名稱,並非資料庫表名
*/
router.post('/get',
async (ctx) => {
const { body } = ctx.request;
const tableName = Object.keys(body)[0]; //表名
const where = body[tableName]; //查詢條件
// 將表名和查詢條件傳遞給資料庫方法
const data = await services.common.getOne(tableName, where);
ctx.body = { code: 0, data };
});
其中 TableName 是服務端中Model的檔名(並非資料庫中的表名),物件中的欄位都是SQL的查詢條件。
粗略估算一下,如果將管理系統的介面替換成通用介面,那麼可節省至少450個介面,佔總介面的40%左右,並且 Service 中的方法也會大大減少。
已將通用介面的前端程式碼整合到 shin-admin 中,後端程式碼整合到 shin-server 中。