一、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 中。