( 轉 ) 優秀REST風格 API的設計原則

設計優秀的REST風格API很是困難!API是服務提供方和使用方之間的契約,打破該契約將會給服務端開發人員招來很是大的麻煩,這些麻煩來自於使用API的開發人員,由於對API的改動會致使他們的移動app沒法工做。一個好的文檔對於解決這些事情能起到事半功倍的做用,可是絕對多數程序員都不喜歡寫文檔。html

若是想讓服務端的價值更好的體現出來,就要好好設計API。經過這些API,你的服務/核心程序將有可能成爲其餘項目所依賴的平臺;目前的大公司:Facebook、Twitter、Google、Github、Amazon、Netflix等等無不依賴API,若是沒有精心設計的API對外開發它們的數據,這些公司也就不會像今天這麼強大。事實上,整個產業存在的目的就是消費上述平臺提供的數據。java

你提供的API越易用,就會有越多人願意使用它。git

若是在設計API時能遵循本文檔提出的原則,那麼你設計出的API就能讓調用方更容易理解和使用,也能大幅減小調用方對你的抱怨;我已將文檔內容按主題分別進行詳細描述,讀者可選擇本身感興趣的主題,而無需順序閱讀。程序員

本文檔中所使用的術語及其含義以下:github

 

  •   Resource(資源):單個實例對象,例如animal(一隻動物);[逍遙子筆記:Roy ThomasFielding(REST風格的提出者)如此解釋資源的含義:一個資源能夠是一份文檔、一張圖片、一個與時間相關的服務(例如:洛杉磯今天的天氣)等等,資源是實體的概念性映射,而不是實體自己;我的理解:一張圖片是一個資源,可是在我本機中有一個的名字叫作「jason.txt」的、實實在在的文件實體,就不能稱做資源,它是資源實體,簡單而言:「資源」與「資源實體」之間猶如類和對象的關係。]
  •   Collection(集合):集合是一組同類的實例對象,例如animals(一羣動物)。
  •   HTTP:一種在網絡上傳輸的通訊協議;
  •   Consumer(使用方、用戶):可以發送http請求的客戶端程序;[逍遙子筆記:這裏Consumer實際是指API的調用方,若是直譯成消費者更讓人困惑]
  •   Third Party Developer(第三方開發人員):不是你項目項目團隊的成員,但但願使用你的服務的那些開發人員;
  •   Server(服務器):可以被Consumer經過網絡訪問的HTTP服務器/程序;
  •   Endpoint(端點):服務器提供的一個URL,它標識了一個資源(Resource)或者集合(Collection);[逍遙子筆記:這裏理解爲URL最後面的那個字段更合適,例如URL: https://api.example.com/vi/zoos的端點就是:/zoos;]
  •   Idempotent(冪等):屢次重複操做獲得的結果同樣;
  •   URL Segment(URL片斷):從某個URL中取出的一小部分片斷;

 

數據設計和抽象

規劃API的展現形式可能比你想象的要簡單,首先要肯定你的數據是如何設計以及核心程序是如何工做的?在新開發項目中進行API設計會比較容易,若是要對一個已經存在的項目進行修改使之符合REST風格,那麼你就須要在抽象方面多下功夫了。[逍遙子筆記:按照RoyThomas Fielding對REST的設計,REST風格更適用於以數據爲中心的架構,而非以計算爲中心的架構]數據庫

有時,集合(Collection)能夠表示數據庫裏的一張表,資源(Resource)表示表中的一行。[逍遙子筆記:這裏的比喻感受有些不恰當,數據庫的一行對應的是一個資源實體,而非資源!]可是多數狀況不是這樣簡單。事實上,你的API應該是對數據和業務邏輯的「儘量的」抽象。很是重要的一點是:複雜的應用數據將會讓第三方開發人員理解和使用起來很是困難,若是你這麼作了,他們就不想使用你的API了。[逍遙子筆記:設計API時,參數和返回值的數據不該太複雜,不然開發人員發起調用和處理返回結果時都要處理半天,很是麻煩!]json

有些狀況下,服務的數據不能經過API暴露出來。一個常見的例子就是許多API都不容許第三方開發人員建立用戶。[逍遙子筆記:設計API時,須要提供什麼功能時就提供什麼API出來,不要過早、過多暴露沒必要要的API接口,咱們開發過程當中一般會遇到這種狀況:不管可否用到,先把本身服務的全部功能暴露出來再說,說不定就用上了,到時候就免得再修改了!]api

動做(Verbs)

你確定知道HTTP的GET和POST請求,這是兩個經過瀏覽器訪問各類網頁時最經常使用的請求。術語POST甚至變成一我的們的經常使用語,即便不知道互聯網如何工做的用戶也知道能POST信息到朋友的Facebook留言板裏。瀏覽器

你須要瞭解這裏列出的4.5個很是重要的HTTP動做,這裏的0.5個是指PATCH,由於它在功能上與PUT很是相似,剩下4個一般被API開發人員兩兩結合使用[逍遙子筆記:例如GET和POST,PUT和DELETE]。這裏是這些動做以及它們對應的數據庫調用(我認爲大多開發人員更熟悉數據庫操做而不是設計API)。[逍遙子筆記:正式基於做者的這個理解,因此本文的不少地方都是用數據庫來解釋API,其實這個也很是恰當,由於REST架構風格是基於資源的,而數據庫也是資源的一種形式。下面這些解釋中,括號裏的內容就是與該HTTP動詞相似的數據庫操做]緩存

 

  •   GET(SELECT):從服務器獲取一個指定資源或一個資源集合;
  •   POST(CREATE):在服務器上建立一個資源;
  •   PUT(UPDATE):更新服務器上的一個資源,須要提供整個資源;
  •   PATCH(UPDATE):更新服務器上的一個資源,只提供資源中改變的那部分屬性;
  •   DELETE(DELETE):移除服務器上的一個資源;

 

還有兩個不常見的HTTP動做:

 

  •   HEAD – 獲取一個資源的元數據,例如一組hash數據或者資源的最近一次更新時間;
  •   OPTIONS – 獲取當前用戶(Consumer)對資源的訪問權限;

 

一個優秀的API將會充分利用這4.5個HTTP動做讓第三方開發人員與本身的數據交互,而且URL中決不包含動做/動詞。[逍遙子筆記:URL是對資源描述的抽象,資源的描述必定是名詞,若是引入了動詞,那這個URL就表示了一個動做,而非一個資源,這樣就偏離了REST的設計思想]

一般,GET請求可以被瀏覽器緩存(並且一般都會這麼作),例如,當用戶發起第二次POST請求時,緩存的GET請求(依賴於緩存首部)可以加快用戶的訪問速度。一個HEAD請求基本上就是一個沒有返回體的GET請求,所以也能被緩存。

版本控制

不管你在設計什麼系統,也無論你事先作了多麼詳盡的計劃,隨着時間的推移和業務的發展,你的程序總會發生變化,數據關係也會發生變化,資源可能會被添加或者刪除一些屬性。只要軟件還在生存期內而且還有人在用它,開發人員就得面對這些問題,對於API設計來講,尤爲如此。[逍遙子筆記:根據RoyThomas Fielding對資源的解釋:資源描述和資源實體是分開的,而設計REST API是基於資源描述,當資源實體發生變動時,只要修改資源描述和資源實體的映射,就能保證資源描述不變,進而保證所設計的API不變,全部使用API的第三方程序也不須要作任何修改,所以,REST風格就是用於解耦這種服務端和客戶端的關係]

API是一份調用方(Consumer)和服務器之間已達成的契約,更改服務器的API必然會影響其向後兼容性,對契約的破壞將會招導致用方(Consumer)的抱怨,若是你改動很大,他們可能會放棄使用你的服務。爲了確保服務器程序可以進化升級,同時可以讓使用方感到滿意,你須要在引入新版本API的同時繼續讓舊版本的API正常工做。

注意,若是你只是簡單地爲API增長一些新特性,例如爲資源增長新屬性(這些新屬性並不是必須的,沒有它們資源也能工做),或者新增了端點,那就不須要升級API的版本號,由於這些變化並不會破壞向後的兼容性。固然,你仍是須要更新API設計文檔(你和調用方的契約)。[逍遙子筆記:文檔對於API來講過重要了,沒有好的文檔必然沒有好的API,所以API和它的文檔必定同步修改,甚至要先修改文檔]

過一段時間以後,你能夠告訴調用方不建議(deprecate)使用舊版本[逍遙子筆記:就像java裏面的depredated註釋同樣,用於告訴使用方,我不建議你使用它了,過一段時間以後我可能就不支持它了]。不建議使用一個API並不意味着立刻就要關閉它或者下降它的服務質量,而是告訴你的API使用人員他們須要版本升級,舊的版本將在將來一段時間以後被中止服務。[逍遙子筆記:經過過渡期來提醒用戶升級API,明確告訴調用方什麼API處在過渡期,過渡期內新老版本同時都能工做,但老版本在過渡期以後就會被去掉]

在URL中加入版本號是一個優秀的API設計,固然還有另外一個經常使用的解決辦法就是把版本號放在請求首部中[逍遙子筆記:HTTP請求的accept字段],根據多年與第三方開發人員打交道的經驗,我能夠告訴你把版本號放在URL裏要比放在請求的首部中更容易實現和使用。[逍遙子筆記:對這種方法還有一些疑問,我的理解,版本應該標識資源,也就是版本是針對URL尾部的端點,例如https://api.example.com/vi/zoos中的/zoos,按照這種思路,當一個項目包含不少類型的資源,這些資源須要要分級進行展現,此時版本號放在URL裏可能遇到不少問題,例如:有個即時通訊項目IM,它包含user_info(用戶信息),user_rel(用戶關係),message三個資源大類,其中user_info又分爲公開信息public和私有信息private兩個類型,public類型包括我的簡介的二維碼信息self_info,private類型包括我的的應用鎖信息lock, user_rel(用戶關係)類型包括好友關係friends和羣組groups兩個類型,groups包含羣成員member和羣信息info兩個子類型資源,messge包含新聊天消息new和歷史聊天消息history,這些資源組織成樹狀結構後以下圖所示:

 

假設個人URL根(在本文後面將會介紹URL的根)爲:https://test.jason.com/im/*,那麼上述資源對應的URL爲:

(1)https://test.jason.com/im/user_info/public/self_info 

該URL表示資源:用戶我的簡介的二維碼信息;

(2)https://test.jason.com/im/user_info/private/lock

該URL表示資源:用戶的應用鎖信息;

(3)https://test.jason.com/im/user_rel/friends

該URL表示資源:用戶的好友信息

(4)https://test.jason.com/im/user_rel/groups/member

該URL表示資源:羣組的羣成員

(5)https://test.jason.com/im/user_rel/groups/info

該URL表示資源:羣組的信息

(6)https://test.jason.com/im/message/new

該URL表示資源:用戶新聊天消息

(7)https://test.jason.com/im/message/history

該URL表示資源:用戶的歷史聊天消息

問題是:在這種狀況下版本號應該放在URL的那個地方?

若是版本號是針對IM整個項目,例如這裏的IM項目總體分爲v1和v2兩個大版本,此時的URL首部就能改爲:https://test.jason.com/im/v1/*,https://test.jason.com/im/v2/*,實際的URL資源將變成:https://test.jason.com/im/v1/message/new。

若是版本號是針對IM項目中的某個子類,例如這裏的message類型分爲v1和v2兩個版本,那麼消息類的URL根就會變成:https://test.jason.com/im/message/v1/*,https://test.jason.com/im/message/v2/*,實際的URL資源將會變成:https://test.jason.com/im/message/v1/new。

若是版本號針對的資源類型更詳細,那麼版本號就會更靠後。在一些負責類型的項目中,資源的類型也會很是複雜,層級也更深,按照本文做者建議的方法就很難肯定v1的位置]

分析

跟蹤各版本/端點的API被調用的狀況[逍遙子筆記:因而可知版本號對應URL末尾的「端點」]。能夠經過在數據庫中爲每一個API增長一個計數器來實現,來一個請求就將對應的使用計數加1。統計各API的調用狀況會帶來不少好處,例如,優化調用頻度最高的API。

爲了構建第三方開發人員喜歡的API,最重要事情是肯定什麼時候不建議(deprecate)用戶使用舊版本API,你可使用這些不建議的(deprecated)API來告知第三方開發人員,這是在你廢掉舊版本以前提醒他們的一個好途徑。

通知第三方開發人員的過程能夠自動化完成,例如,每調用10,000次deprecated API就給相應開發人員發一個郵件提醒。

API的根URL

不管你是否相信,API的根設計很是重要。當開發人員接手一個使用你的API所開發的舊項目,而且須要爲它增長新特性的時候,他可能徹底不瞭解你的服務,或許他們知道的就是所調用的一系列URL。重要的是你API的URL根應該儘量簡單,一個又長又複雜的URL看起來就嚇人,它極可能就把這些第三方開發人員嚇跑了。

這裏有兩個普通的URL根:

 

  •   https//example.org/api/vi/*
  •   https//example.com/vi/*

 

若是你的應用程序很龐大,或者將來它可能變得很龐大,你能夠把API放在各自的子域內,這麼作可讓你的程序在之後的發展中更靈活、更容易擴展。[逍遙子筆記:這裏做者想表達的意思好像是把URL所表示的資源進行分類、分層級,不一樣的資源放在不一樣的類中]

若是你的程序不會變得這麼大,或者你想簡化程序的使用(例如,你想經過一個框架同時提供網站和API服務),就把你的API放在URL的根域(例如:/api/)以後。

最好讓你API的根也包含內容。例如,訪問githubAPI的根就會獲得一個端點(端點表明資源)列表。我更偏好使用根URL獲取那些對「正在迷茫中的」開發人員來講有用的信息,例如:怎麼獲取API的開發者文檔。

注意使用HTTPS前綴,一個好的RESTfulAPI必須使用HTTPS做爲前綴。[逍遙子筆記:例如:https://api.example.com/v1/zoos]

端點

端點是URL中用於標識一個特定資源或資源集合的那部分URL片斷。[逍遙子筆記:例如:https://api.example.com/v1/zoos中的/zoos]

假如你想構建用於表示多個動物園資源的API,其中,每一個動物園都包含不少動物(每一個動物只能屬於一個動物園),顧員(他們能夠在多個動物園工做),而且須要跟蹤每一個動物的詳細信息,那麼這些API的端點可能以下所示:[逍遙子筆記:下列URL中紅色加粗的後綴就是端點]

 

 

在介紹這些端點的做用時,你須要給出這些端點以及操做它們的HTTP動做。例以下面給出的是剛纔所構建動物園API列表的功能,注意,我在每一個端點前面都加上了HTTP動做,就像HTTP請求中所使用的那樣。

 

  •   GET    /zoos:列出全部的動物園(包括動物園的ID、名稱以及簡介);
  •   POST   /zoos: 建立一個新的動物園;
  •   GET    /zoos/ZID:獲取一個完整的動物園對象;[逍遙子筆記:這裏的動物園對象僅僅指「動物園」,例如「動物園」的ID、名稱、介紹等信息,而不包含該動物園中的動物、僱員等下轄類型的信息]
  •   PUT    /zoos/ZID:更新一個動物對象(包含所有信息);
  •   PATCH  /zoos/ZID:更新一個動物對象(包含部分信息);
  •   DELETE /zoos/ZID:          刪除一個動物園對象;
  •   GET    /zoos/ZID/animals:獲取指定動物園的所有動物信息(包括動物的ID和名稱);
  •   GET    /animals:是指列出全部動物信息;
  •   POST  /animals: 建立一個新的動物;
  •   GET   /animals/AID: 獲取某個動物的對象信息;
  •   PUT   /animals/AID:更新一個動物的對象信息(提供對象的所有內容);
  •   PATCH  /animals/AID:更新一個動物的對象信息(提供對象的部份內容);
  •   GET    /animal_types: 獲取全部的動物類型信息;
  •   GET    /animal_types/ATID: 獲取指定動物類型的類型描述信息;
  •   GET    /employees:獲取全部的僱員列表;
  •   GET    /employees/EID:獲取一個指定僱員的信息;
  •   GET    /zoos/ZID/employees:獲取指定動物園的所有僱員列表;
  •   POST   /employees:建立一個新僱員;
  •   POST   /zoos/ZID/employees:爲指定動物園增長一個僱員;
  •   DELETE /zoos/ZID/employees/EID:解僱指定動物園的某個僱員;

 

上述列表中,ZID表示動物園的ID,AID表示動物ID,EID表示僱員ID,ATID表示動物類型ID,在文檔中給出關鍵詞及含義是一個很是好的習慣。

在上面的例子中我已簡要列出常見API的URL前綴,這種簡化方式(省略URL前綴)很是有利於溝通,可是在你的API文檔當中,仍是要使用所有的URL(例如:GET https://api.example.com/vi/animal_type/ATID)。[逍遙子筆記:在非正式文檔中介紹一個API時能夠採用端點代替完成URL這種方法:HTTP動做+端點+該端點的功能說明,端點要比完整URL短的多,這樣更容易表述,也不影響理解,可是在正式文檔中仍是要把URL寫全]

這裏須要注意數據之間關係的展現,尤爲是僱員和動物園之間的多對多的關係。你能夠經過增長URL的方式來表示更多的數據關係[逍遙子筆記:根據RoyThomas Fielding對資源的解釋,關係也是一種資源,所以,在遇到多對多的數據關係時,能夠將數據關係進行拆分,併爲每一個關係都增長一個URL]。固然,這裏並無一個HTTP操做能表示解除僱員,可是咱們能夠經過刪除指定動物園的僱員的方式來達到一樣的效果。[逍遙子筆記:這是對上面「DELETE/zoos/ZID/employees/EID:解僱指定動物園的某個僱員」這個條目的解釋]

過濾器

當用戶請求獲取一組對象列表時,你就須要對結果進行過濾並返回一組嚴格符合用戶要求的對象。有時返回結果的數量可能很是大,可是你也不能隨意對此進行約束,由於這種服務端的隨意約束會形成第三方開發人員的困惑。若是用戶請求了一個集合,並對返回結果進行遍歷,而後只要前100個對象,那麼這裏就須要由用戶來指明這個限制量。這樣用戶就不會有這樣的疑惑:是他們程序的bug仍是接口限制了100條?仍是網絡只容許傳這麼大的包?[逍遙子筆記:在IM項目中有個接口讓用戶拉取本身的歷史消息,咱們就須要在接口中增長一個參數讓用戶來肯定本次要拉取多少條歷史消息,服務器端根據用戶傳入的限制量來肯定返回消息的條數,而不是由服務器來在實現時就肯定該接口一次調用只能返回幾條]

儘可能減小對第三方開發人員的隨意約束。[逍遙子筆記:不要在接口中添加默認的約束條件]

很是重要的一點:讓第三方開發人員本身指定排序過濾器/返回結果集的約束條件。這麼作的最重要緣由是:用戶能用盡可能少的網絡消耗盡快獲取到結果;第二個重要緣由是:用戶可能很懶,想讓服務端幫他們作好分頁和過濾;還有一個對客戶端不那麼重要,可是對服務端很重要的緣由:這麼作會下降請求的資源負載。

過濾器一般用於過濾GET請求返回的資源集合,在GET請求中,能夠經過URL傳遞過濾器信息。你能夠放心把下面例子中列出的過濾器類型應用到本身的API中:

 

  •   ?limit=10:限制返回給用戶的結果集的數量(一般用於分頁);
  •   ?offset=10:給用戶返回一個結果集(一般用於分頁);[逍遙子筆記:指定偏移量,從指定位置開始返回結果集,與limit結合使用就能夠達到分頁效果,第一次從開始獲取指定數量的結果,後續都要從上次結果以後開始返回]
  •   ?animal_type_id=1:返回符合條件的結果集(相似於數據庫的WHERE查詢條件:WHERE animal_type_id=1);
  •   ?sortby=name&order=asc:將結果集按照指定屬性和指定排序方式進行排序(相似於數據的ORDER BY name ASC);

 

上述部分過濾器與前面介紹的某些URL端點功能重複,例如前面提到的URL:GET /zoo/ZID/animals在功能上就與使用過濾器的GET /animals?zoo_id=ZID重複。功能單一的端點對於第三方開發人員來講更容易使用,尤爲是他們使用你提供的請求作一些複雜的開發時,更是如此。在API文檔中明確寫出這些功能重複的請求方式,將會消除第三方開發人員對這些重複功能的困惑,不然他們就會懷疑這些重複功能之間是否有差別!

還有一點,當須要對數據進行過濾或者排序時,你要給第三方開發人員(Consumer)列出哪些屬性能用於過濾或排序,咱們不但願把任何數據庫操做的錯誤返回給用戶。[逍遙子筆記:不要把服務內部的錯誤或者問題暴露給第三方開發人員]

狀態碼

充分利用適當的狀態碼對於設計REST 風格的API來講很是重要,由於HTTP狀態碼已有標準定義,而且各類網絡設備都能讀取並識別這些它們。例如,經過配置負載均衡器的參數來避免將請求發往出現50X錯誤的服務程序[逍遙子筆記:50X表示服務程序內部出錯]。這裏將列出一些可供你選擇使用的HTTP狀態碼,它們能夠成爲你設計良好返回碼的出發點:

 

  •   200 OK – [GET]

 

    客戶端向服務器請求數據時,服務器找到這些數據並將之返回給客戶端(此行爲冪等);[逍遙子筆記:GET操做只能獲取數據(即只讀),不該該對服務器的數據進行任何形式的修改]

 

  •   201 CREATED – [POST/PUT/PATCH]

 

    客戶端向服務器發送數據,服務器爲這些數據建立一個資源;

 

  •   204 NO CONTENT – [DELETE]

 

    客戶端請求服務器刪除一個資源時,服務器將該資源刪除;[逍遙子筆記:返回碼204表示執行成功了,可是沒有數據。HTTP 的RFC2616中對於204返回碼的描述爲:若是客戶端是個代理(例如瀏覽器),它不該該改變「觸發該請求的」頁面展現,該返回值主要用於輸入行爲發生時,雖然新的或更新過的元數據信息被應用於當前頁面,但代理(瀏覽器)不能改變當前的頁面展現,原文爲:

If the client is a user agent, itSHOULD NOT change its document view from that which caused the request to besent. This response is primarily intended to allow input for actions to takeplace without causing a change to the user agent’s active document view,although any new or updated metainformation SHOULD be applied to the documentcurrently in the user agent’s active view.]

 

  •   400 INVALID REQUEST – [POST/PUT/PATCH]

 

    客戶端給服務器發送了一個無效的請求,服務器對此不做任何動做(此行爲冪等)。

 

  •   404 NOT FOUND – [*]

 

    客戶端請求了一個不存在的資源或資源集合,服務端對此不做任何動做(此行爲冪等)。

 

  •   500 INTERNAL SERVER ERROR – [*]

 

    服務器內部發生了錯誤,客戶端沒法知道請求是否被執行成功了。

狀態碼範圍

1XX的返回碼預留給HTTP的底層使用,在你的整個職業生涯中都不會主動發送這種返回碼;

2XX的返回碼錶示請求按照預期執行併成功返回了信息。服務端要儘量給用戶返回這種結果。

3XX的返回碼錶示請求重定向,大多數API都不會常用這種請求(),可是最新的超媒體API會充分使用這些功能。

4XX的返回碼主要表示由客戶端引發的錯誤,例如請求參數錯誤或者訪問一個不存在的資源,這些必須爲冪等操做,而且不能改變服務器的狀態[逍遙子筆記:其實服務器的狀態發生了改變就意味着操做不是冪等了]。

5XX的返回碼主要表示由服務器引發的錯誤,一般狀況下,這些錯誤都是開發人員([逍遙子筆記:這裏應該是服務器程序的開發人員])接觸不到的底層函數拋出來,而後傳遞給用戶([逍遙子筆記:這裏應該是第三方開發人員])的。用戶在收到5XX的返回碼時,他們不知道服務器當前的工做狀態是否正常,所以,要儘可能避免這種狀況發生。

返回值文檔

當第三方開發人員在發送HTTP請求時,他們須要事先了解這些請求的返回值信息,下述列表就是一些REST風格API以及它們對應的返回值信息:[逍遙子筆記:與前面的介紹URL的功能相似,只是這裏將最後面的URL描述換成返回值描述,這裏依然採用三段式描述法:HTTP請求動做+端點+端點對應的返回值描述信息]

 

  •   GET /collection:返回一個資源對象的列表;
  •   GET /collection/resource: 返回一個資源對象;
  •   POST /collection:返回新建立的資源對象
  •   PUT /collection/resource:返回一個完整的資源對象;
  •   PATCH /collection/resource:返回一個完整的資源對象;[逍遙子筆記:雖然請求中只帶了部分資源對象的內容,可是返回的內容倒是整個資源對象]
  •   DELETE /collection/resource:返回一個空文檔;

 

須要注意的是:當用戶建立一個資源時,他們一般並不想知道新建立資源的ID(也不想知道其餘屬性,例如修改或建立的時間戳)[逍遙子筆記:這一點略有疑惑,這種設計思路應該也是區分場合的,若是資源很複雜,本文介紹的這種思路或許可行,若是資源本來就很簡單,例如咱們之前設計的發送消息接口,就直接給用戶返回所發送消息的ID]。這些新增的屬性信息可經過後續請求得到,固然也能夠經過初始化POST請求來返回。

受權

多數狀況下,服務器想確切知道每一個請求的發起方是誰?固然,部分API接口能放開被用匿名訪問,但一般接口只能被受權的用戶訪問。

OAuth2.0爲此提供了一個很好的實現途徑。你能知道每一個請求是由哪一個客戶端發起?這些請求背後分別表明了哪些用戶?以及提供一種標準化的用戶訪問或撤銷訪問方式,全部這些都無需第三方用戶的登陸授信。

還有OAuth1.0和xAuth也能完成相似功能。不管採用那種方法,必定要確保通用性以和良好的文檔設計,在文檔中對用戶經常使用語言/平臺的各類不一樣封裝庫進行詳細說明。[逍遙子筆記:這些服務以庫的方式供客戶端調用,例如SDK,所以在文檔中要對各類形式的封裝庫進行詳細說明]

我能夠如實地告訴你,儘管OAuth1.0a雖然是最安全的選項,可是它很是難以部署。我遇到不少第三方開發人員抱怨他們不得不實現本身的庫,由於OAuth1.0a沒有他們所使用語言對應的庫。我花費了大量的時間用於解決那些難以理解的「invalid signature」錯誤。所以,我建議你使用其餘的替代方案。

內容類型

目前,大多數REST風格API接口都提供JSON格式的數據,你所能想起來的Facebook、Twitter、GitHub都是如此。XML方式已經逐漸退出人們的視野(除了一些大公司內部還在使用以外),幸好SOAP方式已經消失,咱們已經看不到返回HTML格式數據的API接口了。

開發者經常使用的開發語言或框架都能輕易解析你返回的各類有效數據。若是你正在使用不一樣的序列化器構建一個通用的返回對象,你可使用前面提到的任意數據格式(SOAP除外),不過在返回數據時須要注意處理請求首部的Accept字段。[逍遙子筆記:HTTP請求頭部的Accept可用於指定返回數據的格式]

一些API開發人員建議針對不一樣返回內容的類型,爲URL(在端點以後)添加擴展字段,例如:.json,.xml,或者.html,但我不建議這麼作,我建議使用HTTP請求首部的Accept字段(HTTP的RFC文檔中有對Accept的解釋),而且以爲這纔是合適的方法。

超媒體API

超媒體API可能表明REST風格API的將來發展,它們在思想上更符合HTTP和HTML的設計初衷。[逍遙子筆記:根據REST做者Roy Thomas Fielding的描述,REST核心是面向資源的設計,超媒體服務所提供了各類多媒體資源的訪問,它在本質上符合了以資源爲中心的設計]

在使用非超媒體的REST風格API時,URL端點也是客戶端和服務器之間契約的一部分,這些端點必須事先告知客戶端,一旦改變它們,客戶端就沒法按預期與服務器進行交互,這其實也是一種約束。

如今,API的用戶不只僅是能發起HTTP請求的用戶代理,人們更經常使用瀏覽器發起HTTP請求。然而,用戶不會被這些預先定義的、REST風格API的URL端點所約束。是什麼讓用戶變得如此特殊呢?如今的網頁能讓用戶先讀主題,而後點擊他們感興趣的主題所對應的連接,訪問他們想訪問的網站或者想閱讀的內容,此時URL發生變化的時候,用戶不受影響(除非用戶爲某個網頁打了標籤,在他們訪問這些打了標籤的網頁時會自動跳轉到主頁,用戶須要再從主頁中尋找他們所感興趣數據的新路徑)。[逍遙子筆記:咱們的網站一般採用這種方式,尤爲是各類門戶網站,例如網易:www.163.com,新浪:www.sina.com,打開這些門戶網站,咱們看不到一個URL,咱們所看到的都是一個個的主題,每一個主題對應一個超連接(URL),當這些主題對應的URL發生變化時,只須要調整主題和超連接(URL)的映射關係便可,用戶實際上看不到這些URL的變化]

超媒體API概念和一個普通人的行爲相似。請求API的根目錄將會得到一個URL列表,這些URL列表的每一項均可能對應了一個信息集,而且它以用戶可以理解的方式來描述這些信息集合。無需爲每一個資源提供ID(除非特別邀請),由於一個URL就惟一標識了一個資源。

超媒體API用戶在訪問鏈接而且收集信息時,返回結果中將被放入最近更新的URL連接,所以URL無需事先與用戶約定。若是URL被緩存了,後續請求又返回了404[逍遙子筆記:404表示請求了一個不存在的資源],用戶只需簡單地回到根目錄從新尋找內容便可。

當從集合中檢索一個資源列表時,要給用戶返回這個資源列表的完整URL。當執行POST/PATCH/PUT請求時,可用返回碼爲3XX的響應來重定向到新的資源。

JSON即沒法給咱們提供須要的語義來指明哪些屬性是URL,也不能說明URL怎樣與當前的文檔關聯起來;可能正如你猜想的那樣,HTML能夠提供這樣的信息。咱們能夠先經過API拿到數據,而後再進行HTML處理。想一想CSS陪伴咱們走過的這些路,咱們可能有一天會看到:在獲取一樣URL和內容的時候,不管經過API請求仍是網站訪問,都採用同一種處理方式。[逍遙子筆記:JSON提供數據內容,並未提供數據展現功能]

文檔

老實說,若是沒有徹底按照本文檔的指導,你的API也不必定會太差,然而,若是你沒有爲API編寫合適的文檔,沒有人會願意用它,它將變爲一個極難使用的API。

確保你的文檔無需受權即可被開發人員訪問。

不要使用自動文檔生成器,若是用了,就必定要仔細檢查和修改生成的文檔,確保它們可以被用戶理解和使用。

文檔中所舉例子中的請求和響應包體要寫全,不要截斷不重要的部分,而是用高亮方式展現重要的部分。

文檔中要寫明各URL端點的預期返回碼和可能的出錯信息,以及出現這些錯誤信息的可能緣由是什麼?

若是時間充足,你還能夠構建一個第三方開發人員使用的API控制檯,以便他們能夠當即對你的API進行驗證。這個事情作起來不會像你想象的那麼難,可是開發人員(包括內部開發人員和第三方開發人員)卻可能由於這個功能而喜歡上使用你的API。

確保文檔能被打印,例如,CSS就是一種強大的文檔展現方式;在文檔打印的時候必定要隱藏文檔的工具欄,即使沒有人把你的文檔用打印機打印出來,也會有不少開發人員喜歡把它們輸出爲PDF以便離線查看。

 

原貼 : https://blog.csdn.net/houjixin/article/details/54315835

相關文章
相關標籤/搜索