由 GraphQL 來思考如何作一個好的 API Design

目前我已經寫了一年多 graphql,也時常思考和 Rest API 的不一樣,以及對 API Design 的啓發。javascript

他山之石能夠攻玉。qraphql 一些自然的設計或者思想對寫 Rest API 有很大的借鑑或參考意義。html

這裏總結下一些受啓發的 API 設計規範。前端

若是你對 graphql 不熟悉,能夠先參考 graphql 中文文檔java

本文連接 shanyue.tech/post/api-de…node

對全部的資源返回 id

在 graphql 中,scalar 類型 ID 用來表示資源的全局惟一性。在 apollo-client 中也建議客戶端每次請求都把 id 帶上。git

在響應中帶上 id 至少有兩個好處github

  1. 客戶端對資源的緩存
  2. 在數據上游至客戶端的整個鏈路中有利於數據的溯源

按需加載資源的字段

query TODO {
  todo (id: 10) {
    id 
    name
    status
  }
}
複製代碼

如客戶端只須要顯示某個 TODO 的狀態以及名稱,則只須要返回 name 以及 status 字段,大大減小了網絡的流量。數據庫

另外, graphql server 須要在數據庫層面也對字段作按需加載。不然,graphql server 與 database 之間也會形成無用的數據 IO 與流量浪費。json

獲取 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 的 QueryMutation 都是使用 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狀態碼的選擇,取一張圖出來

如何選擇http錯誤狀態碼

請求及響應數據校驗

因爲 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。

相關文章
相關標籤/搜索