目前我已經寫了一年多 graphql,也時常思考和 Rest API 的不同,以及對 API Design 的啟發。
他山之石可以攻玉。qraphql 一些天然的設計或者思想對寫 Rest API 有很大的借鑑或參考意義。
這裡總結下一些受啟發的 API 設計規範。
如果你對 graphql 不熟悉,可以先參考 graphql 中文文件
對所有的資源返回 id
在 graphql 中,scalar 型別 ID
用來表示資源的全域性唯一性。在 apollo-client
中也建議客戶端每次請求都把 id 帶上。
在響應中帶上 id 至少有兩個好處
- 客戶端對資源的快取
- 在資料上游至客戶端的整個鏈路中有利於資料的溯源
按需載入資源的欄位
query TODO {
todo (id: 10) {
id
name
status
}
}
複製程式碼
如客戶端只需要顯示某個 TODO 的狀態以及名稱,則只需要返回 name 以及 status 欄位,大大減少了網路的流量。
另外, graphql server 需要在資料庫層面也對欄位做按需載入。否則,graphql server 與 database 之間也會造成無用的資料 IO 與流量浪費。
獲取 graphql query 所請求的欄位,需要手動解析 GraphQLFieldResolveFn 函式的第四個欄位 info,並在每一個 field 上自定義一個 directive 標註 Graphql Filed 與 Database Field 的關係
在 Rest API 中可以使用額外欄位做按需載入。 如使用 fields 標記返回需要的欄位,若無此欄位,預設返回資源的全部欄位,在中介軟體中對 fields 做結構化處理
// 請求 Todo:10,並且只需要 id,name,status 三個字元安
'/api/todos/10?fields=id,name,status'
// 請求 Todo:10 全部資源
'/api/todos/10'
複製程式碼
關聯資源使用巢狀物件表示
這個請求表示一個使用者列表,每個使用者需要展示最後一個 Todo 的名稱。Todo 需要使用巢狀物件來表示。
query USERS {
users {
id
name
lastTodo {
id
name
}
}
}
複製程式碼
在 Rest API 設計中經常見到所有資料進行了展開,不僅無法定位資源,也不好擴充套件資料。巢狀資料可以很靈活的擴充套件資料,另外也可以對巢狀資料進行按需載入
const res0 = {
users: [{
id: 1,
name: "山月",
todoName: "學習"
}]
}
// 修改後
const todoFields = {}
const res = {
users: [{
id: 1,
name: "山月",
todo: {
id: 1,
name: "學習",
...fields
}
}]
}
// 可以這樣設計 API
const api = '/api/users?fields=id,name,todo.id,todo.name'
複製程式碼
使用 ISOString 表示時間戳
在 graphql 中,雖沒有一個 scalar 型別來表示時間戳,不過可以自定義 scalar DateTime 來表示時間。關於時間的格式
參考 StackOverflow 上的問題 the-right-json-date-format
const date = new Date()
// 從 toJSON 的輸出就知道前後端互動需要使用什麼格式了
date.toJSON()
// 2019-03-14T07:41:08.500Z
date.toISOString()
// 2019-03-14T07:41:08.500Z
複製程式碼
這樣返回的格式不僅符合規範,而且可讀性也比較好。
我見過API中返回的時間戳表示為 unix timestamp,js timestamp, iso8601 三種格式,較為混亂。統一的資料格式有利於前後端的聯調,不過這也得益於 graphql 的強型別 schema。
結構化的錯誤資訊
在 graphql 中會返回 { data, errors }
的資料結構,可以在最後結構化錯誤資訊為
{
"code": "InvalidToken",
"message": "Token 失效",
"httpStatus": 401
}
複製程式碼
message
為可讀性的錯誤資訊,可以由前端直接顯示,code
為除錯用,httpStatus
由下一步的中介軟體捕捉,設定狀態碼。
在結構化錯誤資訊後,可以順帶把錯誤資訊傳送到報警系統 (如 Sentry)。不過需要分清 WARN 與 ERROR,如 401,403 應當做 WARN 處理。
符合標準的 http status
恩,好吧。graphql 這條有缺陷。graphql 的 Query
與 Mutation
都是使用 POST
請求。對不同的執行成功的 Mutation
返回不同的 200,201,202 還是比較麻煩。
不過對於錯誤返回不同的狀態碼, 開啟 devtool 一眼可以看到紅色的 4XX 資訊,也對快速定位錯誤請求有幫助,稍微減少了些煩躁心。
介紹幾種常見的4xx狀態碼
- 401 Unauthorized: 使用者未登入請求需要登入才能請求的資源
- 403 Forbidden: 使用者A登入了,但他卻想請求 B 的資源
- 400 Bad Request: 恩,我把所有找不到狀態碼的錯誤都放到了 400
關於400參考 400 BAD request HTTP error code meaning? 這裡有一篇文章,關於4xx狀態碼的選擇,取一張圖出來
請求及響應資料校驗
由於 graphql 的強型別 schema,也省了資料輸入輸出的校驗。
對於 Rest API,可以使用 JSON Schema 來校驗資料格式。node 也可以使用 joi 做資料校驗。
這裡放一份 JSON Schema 的文件:json-schema.org/
註釋文件化
得益於 graphql 的 introspection 與強型別的 schema。graphql 可以根據原始碼以及註釋自動生成文件,直接使用 graphiql 或者 graphql playground 上檢視。
如果你使用 node.js 來寫伺服器應用,可以使用 apiDoc
另外,注意不要把文件暴露到生產環境,graphql 需要在生產環境中關掉 introspection。