英文原文: HTTP API Design Guide
本文譯者: LeoXu, Garfielt, 無若, --zxphtml
本指南描述了一套有關 HTTP+JSON API 的設計實踐, 原始內容提取自 Heroku 平臺 API 的工做.git
本指南是對API的補充,也是Heroku新的內部API的指南. 咱們但願引發Heroku以外的API設計者的興趣.github
這裏咱們的目標是一致的,專一於業務邏輯而避免脫節的設計. 咱們就是要尋找一個良好的,一致的,文檔優良的方式來設計API,而不必是惟一理想的方式.算法
咱們假定你熟悉HTTP+JSON API的一些基礎,不會再指南中涵蓋全部基礎性的東西.shell
咱們歡迎爲這一指南 作出貢獻.json
對於每一種響應返回適當的HTTP狀態碼. 成功的響應應該根據下面的指南編碼:api
請閱讀指導有關用戶錯誤和服務器錯誤狀況的狀態碼的HTTP 響應碼文檔緩存
儘量在響應中提供完整的資源描述 (例如,帶有全部屬性的對象). 老是在200和201響應中提供完整的資源, 包括 PUT/PATCH 和 DELETE 請求, 例如:服務器
$ curl -X DELETE \ https://service.com/apps/1f9b/domains/0fd4 HTTP/1.1 200 OK Content-Type: application/json;charset=utf-8 ... { "created_at": "2012-01-01T12:00:00Z", "hostname": "subdomain.example.com", "id": "01234567-89ab-cdef-0123-456789abcdef", "updated_at": "2012-01-01T12:00:00Z" }
202 響應不會包含完整的資源描述,例如:app
$ curl -X DELETE \ https://service.com/apps/1f9b/dynos/05bd HTTP/1.1 202 Accepted Content-Type: application/json;charset=utf-8 ... {}
接受PUT/PATCH/POST請求中的序列化JSON, 做爲表單編碼數據的替代或者補充. 這樣就能夠建立對稱的JSON序列化響應,例如:
$ curl -X POST https://service.com/apps \ -H "Content-Type: application/json" \ -d '{"name": "demoapp"}' { "id": "01234567-89ab-cdef-0123-456789abcdef", "name": "demoapp", "owner": { "email": "username@example.com", "id": "01234567-89ab-cdef-0123-456789abcdef" }, ... }
默認給每個資源都指定一個id. 除非你有更好的理由,否則就使用UUID. 不要使用在整個服務或者服務中其它資源那裏不是全局惟一的ID, 特別是自增加的ID.
用小寫 8-4-4-4-12 格式生成UUID,例如:
"id": "01234567-89ab-cdef-0123-456789abcdef"
默認爲資源提供建立和更新的時間戳,例如:
{ ... "created_at": "2012-01-01T12:00:00Z", "updated_at": "2012-01-01T13:00:00Z", ...}
這些時間戳可能對一些資源沒啥用,如此則能夠省去.
只使用UTC接收和返回時間. 使用 ISO8601 格式來生成時間,例如:
"finished_at": "2012-01-01T12:00:00Z"
使用資源名稱的複數形式,除非系統中相關的資源是惟一的(例如,在大多數系統,用戶的帳戶永遠都只能有一個). 這就能在你引用特定的資源時保持一致的方式.
首選端點佈局,由於它不須要對單獨的資源有任何特殊的操做. 有些狀況下是須要特殊操做的,那就把它們放在一個標準的前綴下,以清楚的界定它們:
/resources/:resource/actions/:action
例如.
/runs/{run_id}/actions/stop
使用小寫和用虛線符號分隔的路徑名,便於同主機名對齊, 例如:
service-api.com/users service-api.com/app-setups
屬性一樣也使用小寫,可是使用下劃線作分隔,那就屬性名在Javascript中就能夠不用引號了, 例如:
service_class: "first"
使用一個內聯的對象來序列化外鍵引用,例如:
{ "name": "service-production", "owner": { "id": "5d8201b0..." }, ...}
而不是以下例:
{ "name": "service-production", "owner_id": "5d8201b0...", ...}
這種方式使得在沒必要改變響應結構或者引入更多頂級響應域的前提下內聯如更多相關資源的信息,例如:
{ "name": "service-production", "owner": { "id": "5d8201b0...", "name": "Alice", "email": "alice@heroku.com" }, ...}
在某些狀況下對於端用戶而言提供一個ID標誌一個資源可能會方便些。例如,一個用戶會須要一個Heroku應用名稱,但那個應用時用UUID標識的。在這些狀況下你可能想要同時接受名稱和ID,例如:
$ curl https://service.com/apps/{app_id_or_name} $ curl https://service.com/apps/97addcf0-c182 $ curl https://service.com/apps/www-prod
不要只接受名稱而排斥ID.
使用一致的,結構化的錯誤響應. 包括一個依賴於機器的錯誤id,一我的類可讀的錯誤消息,以及可選的一個指出有關該錯誤及如何解決的信息的url,例如:
HTTP/1.1 429 Too Many Requests
{ "id": "rate_limit", "message": "Account reached its API rate limit.", "url": "https://docs.service.com/rate-limits"}
爲你的錯誤格式,以及客戶端可能會遇到的錯誤id編寫文檔.
在全部的響應中包含一個ETag頭,以標識返回資源的特定版本. 用戶就可以從If-None-Match頭獲取的值中檢查出他們的後續請求的是否已通過時.
在每個API響應中包含一個Request-Id頭,填充一個UUID值。若是服務器和客戶端都記錄了這個值的話,它就能在跟蹤和調試請求方面起到做用.
對容易產生大量數據的響應進行分頁. 使用 Content-Range 頭來傳送分頁請求. 詳細的能夠看看 Heroku 平臺有關範圍的API 中的請求和響應頭, 狀態碼, 限制,排序和分頁瀏覽的示例.
來自客戶端的速率限制請求用以保護服務的健康,併爲其它的客戶端保持較高的服務質量. 你可使用一種 令牌桶算法 來量化請求限制.
能夠在RateLimit-Remaining響應頭中返回每一個請求的剩餘請求令牌數量.
從一開始就要對API進行版本話。使用接收頭,以及一個自定義內容類型來同版本進行交互,例如:
Accept: application/vnd.heroku+json; version=3
不去指定一個默認的版本,而不是要求客戶端明確指定它們要使用一個特定的版本.
在帶有內聯父/子資源關係的數據模型中,路徑可能會內聯得很深,例如:
/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}
能夠經過在根路徑定位資源來限制內聯深度. 使用內聯來指定範圍集合.例如,上述狀況中一個dyno就屬於一個屬於org的app:
/orgs/{org_id} /orgs/{org_id}/apps /apps/{app_id} /apps/{app_id}/dynos /dynos/{dyno_id}
提供一個機器可讀的模式能夠精確的指定你的API。使用 prmd 來管理你的模式,並確保它能被prmd verify驗證.
提供客戶端開發者能夠用來理解你的API的人類可讀文檔.
若是你使用prmd建立了一個如上所述的模式,那麼你就能夠很容易的使用prmd doc來爲全部的端點生成Markdown文檔.
除了端點的詳細信息以外,還要提供API概述的一些信息:
提供可執行的示例,用戶能夠直接在終端中敲入命令來查看API的調用如何運行. 爲了儘量的擴展,這些示例應該要能夠照字面意義使用,以最小化用戶嘗試這些API所須要作的事情,例如:
$ export TOKEN=... # acquire from dashboard $ curl -is https://$TOKEN@service.com/users
若是你使用 prmd 生成了Markdown文檔,你就將能夠不費力的得到每個端點的示例.
描述API的穩定性或者依據其成熟性和穩定性的各個點,例如:以原型/開發/成品做爲標誌節點。
查看Heroku API兼容性策略爲穩定性和變動管理方法提供一種可能。
一旦你的API被定義爲是爲生產所準備的和堅固的,那當API版本改變的時候,要使得這些API能有向後的兼容性。在建立一個新的API時,若是你須要作向後不兼容的變動,應增長版本號。
無一例外,要SSL去訪問API時,不管用不用SSL,都沒必要找出以及解釋其緣由,它們就是須要SSL。
用戶第一次查看你的api極可能是在使用curl的命令行裏。若是API的響應有良好的打印格式,那在命令行裏它們會很容易理解。爲了給這些開發者提供方便,良好打印格式的JSON以下:
{ "beta": false, "email": "alice@heroku.com", "id": "01234567-89ab-cdef-0123-456789abcdef", "last_login": "2012-01-01T12:00:00Z", "created_at": "2012-01-01T12:00:00Z", "updated_at": "2012-01-01T12:00:00Z"}
而不是:
{"beta":false,"email":"alice@heroku.com","id":"01234567-89ab-cdef-0123-456789abcdef","last_login":"2012-01-01T12:00:00Z", "created_at":"2012-01-01T12:00:00Z","updated_at":"2012-01-01T12:00:00Z"}
要確保在JSON結尾有換行,以防止阻塞用戶的終端界面。
對於大部分API的響應,性能考濾要優先於良好打印。在某些結點(例如高流量結點)或爲某些特定用戶(例如無GUI界面的程序)使用時,你可能會考濾使用高性能而非良好打印的API。
注:headless program譯爲「無顯示界面的程序」,參考自這篇文章.