好RESTful API的設計原則

640?wx_fmt=jpeg

作出一個好的API設計很難。API表達的是你的數據和你的數據使用者之間的契約。打破這個契約將會招致不少憤怒的郵件,和一大堆傷心的用戶-由於他們手機上的App不工做了。而文檔化只能達到一半的效果,而且也很難找到一個願意寫文檔的程序員。
html

你所能作的最重要一件事來提升服務的價值就是建立一個API。由於隨着其餘服務的成長,有這樣一個API會使你的服務或者核心應用將有機會變成一個平臺。環顧一下現有的這些大公司:Facebook,Twitter,Google, Github,Amazon,Netflix等。若是當時他們沒有經過API來開放數據的話,也不可能成長到現在的規模。事實上,整個行業存在的惟一目的就是消費所謂平臺上的數據。程序員

你的API越容易使用,那麼就會有越多的人去用它。web

本文提到的這些原則,若是你的API能嚴格按照這些原則來設計,使用者就能夠知道它接下來要作什麼,而且能減小大量沒必要要的疑惑或者是憤怒的郵件。我已經把全部內容都整理到不一樣的主題裏了,你無需按順序去閱讀它。數據庫

定義

這裏有一些很是重要的術語,我將在本文裏面一直用到它們:json

  • 資源:一個對象的單獨實例,如一隻動物api

  • 集合:一羣同種對象,如動物瀏覽器

  • HTTP:跨網絡的通訊協議緩存

  • 客戶端:能夠建立HTTP請求的客戶端應用程序服務器

  • 第三方開發者:這個開發者不屬於你的項目可是有想使用你的數據網絡

  • 服務器:一個HTTP服務器或者應用程序,客戶端能夠跨網絡訪問它

  • 端點:這個API在服務器上的URL用於表達一個資源或者一個集合

  • 冪等:無邊際效應,屢次操做獲得相同的結果

  • URL段:在URL裏面已斜槓分隔的內容

數據設計與抽象

規劃好你的API的外觀要先於開發它實際的功能。首先你要知道數據該如何設計和核心服務/應用程序會如何工做。若是你純粹新開發一個API,這樣會比較容易一些。但若是你是往已有的項目中增長API,你可能須要提供更多的抽象。

有時候一個集合能夠表達一個數據庫表,而一個資源能夠表達成裏面的一行記錄,可是這並非常態。事實上,你的API應該儘量經過抽象來分離數據與業務邏輯。這點很是重要,只有這樣作你纔不會打擊到那些擁有複雜業務的第三方開發者,不然他們是不會使用你的API的。

固然你的服務可能不少部分是不該該經過API暴露出去的。比較常見的例子就是不少API是不容許第三方來建立用戶的。

動詞

顯然你瞭解GET和POST請求。當你用瀏覽器去訪問不一樣頁面的時候,這兩個是最多見的請求。POST術語如此流行以致於開始侵擾通俗用語。即便是那些不知道互聯網如何工做的人們也能「post」一些東西到朋友的Facebook牆上。

這裏至少有四個半很是重要的HTTP動詞須要你知道。我之因此說「半個」的意思是PATCH這個動詞很是相似於PUT,而且它們倆也經常被開發者綁定到同一個API上。

  • GET (選擇):從服務器上獲取一個具體的資源或者一個資源列表。

  • POST (建立): 在服務器上建立一個新的資源。

  • PUT (更新):以總體的方式更新服務器上的一個資源。

  • PATCH (更新):只更新服務器上一個資源的一個屬性。

  • DELETE (刪除):刪除服務器上的一個資源。

還有兩個不經常使用的HTTP動詞:

  • HEAD : 獲取一個資源的元數據,如數據的哈希值或最後的更新時間。

  • OPTIONS:獲取客戶端能對資源作什麼操做的信息。

一個好的RESTful API只容許第三方調用者使用這四個半HTTP動詞進行數據交互,而且在URL段裏面不出現任何其餘的動詞。

通常來講,GET請求能夠被瀏覽器緩存(一般也是這樣的)。例如,緩存請求頭用於第二次用戶的POST請求。HEAD請求是基於一個無響應體的GET請求,而且也能夠被緩存的。

版本化

不管你正在構建什麼,不管你在入手前作了多少計劃,你核心的應用總會發生變化,數據關係也會變化,資源上的屬性也會被增長或刪除。只要你的項目還活着,而且有大量的用戶在用,這種狀況老是會發生。

請謹記一點,API是服務器與客戶端之間的一個公共契約。若是你對服務器上的API作了一個更改,而且這些更改沒法向後兼容,那麼你就打破了這個契約,客戶端又會要求你從新支持它。爲了不這樣的事情,你既要確保應用程序逐步的演變,又要讓客戶端滿意。那麼你必須在引入新版本API的同時保持舊版本API仍然可用。

注:若是你只是簡單的增長一個新的特性到API上,如資源上的一個新屬性或者增長一個新的端點,你不須要增長API的版本。由於這些並不會形成向後兼容性的問題,你只須要修改文檔便可。

隨着時間的推移,你可能聲明再也不支持某些舊版本的API。申明不支持一個特性並不意味着關閉或者破壞它。而是告訴客戶端舊版本的API將在某個特定的時間被刪除,而且建議他們使用新版本的API。

一個好的RESTful API會在URL中包含版本信息。另外一種比較常見的方案是在請求頭裏面保持版本信息。可是跟不少不一樣的第三方開發者一塊兒工做後,我能夠很明確的告訴你,在請求頭裏麪包含版本信息遠沒有放在URL裏面來的容易。

分析

所謂API分析就是持續跟蹤那些正爲人使用的API的版本和端點信息。而這可能就跟每次請求都往數據庫增長一個整數那樣簡單。有不少的緣由顯示API跟蹤分析是一個好主意,例如,對那些使用最普遍的API來講效率是最重要的。

第三方開發者一般會關注API的構建目的,其中最重要的一個目的是你決定何時再也不支持某個版本。你須要明確的告知開發者他們正在使用那些即將被移除的API特性。這是一個很好的方式在你準備刪除舊的API以前去提醒他們進行升級。

固然第三方開發者的通知流程能夠以某種條件被自動觸發,例如每當一個過期的特性上發生10000次請求時就發郵件通知開發者。

API根URL

不管你信不信,API的根地址很重要。當一個開發者接手了一箇舊項目(如進行代碼考古時)。而這個項目正在使用你的API,同時開發者還想構建一個新的特性,但他們徹底不知道你的服務。幸運的是他們知道客戶端對外調用的那些URL列表。讓你的API根入口點保持儘量的簡單是很重要的,由於開發者極可能一看到那些冗長而又複雜的URL就轉身而走。

這裏有兩個常見的URL根例子:

  • https://example.org/api/v1/*

  • https://api.example.com/v1/*

若是你的應用很龐大或者你預期它將會變的很龐大,那麼將API放到子域下一般是一個好選擇。這種作法能夠保持某些規模化上的靈活性。

但若是你以爲你的API不會變的很龐大,或是你只是想讓應用安裝更簡單些(如你想用相同的框架來支持站點和API),將你的API放到根域名下也是能夠的。

讓API根擁有一些內容一般也是個好主意。Github的API根就是一個典型的例子。從我的角度來講我是一個經過根URL發佈信息的粉絲,這對不少人來講是有用的,例如如何獲取API相關的開發文檔。

一樣也請注意HTTPS前綴,一個好的RESTful API老是基於HTTPS來發布的。

端點

一個端點就是指向特定資源或資源集合的URL。

若是你正在構建一個虛構的API來展示幾個不一樣的動物園,每個動物園又包含不少動物,員工和每一個動物的物種,你可能會有以下的端點信息:

  • https://api.example.com/v1/zoos

  • https://api.example.com/v1/animals

  • https://api.example.com/v1/animal_types

  • https://api.example.com/v1/employees

針對每個端點來講,你可能想列出全部可行的HTTP動詞和端點的組合。以下所示,請注意我把HTTP動詞都放在了虛構的API以前,正如將一樣的註解放在每個HTTP請求頭裏同樣。(下面的URL就不翻譯了,我以爲沒啥必要翻^_^)

  • GET /zoos: List all Zoos (ID and Name, not too much detail)

  • POST /zoos: Create a new Zoo

  • GET /zoos/ZID: Retrieve an entire Zoo object

  • PUT /zoos/ZID: Update a Zoo (entire object)

  • PATCH /zoos/ZID: Update a Zoo (partial object)

  • DELETE /zoos/ZID: Delete a Zoo

  • GET /zoos/ZID/animals: Retrieve a listing of Animals (ID and Name).

  • GET /animals: List all Animals (ID and Name).

  • POST /animals: Create a new Animal

  • GET /animals/AID: Retrieve an Animal object

  • PUT /animals/AID: Update an Animal (entire object)

  • PATCH /animals/AID: Update an Animal (partial object)

  • GET /animal_types: Retrieve a listing (ID and Name) of all Animal Types

  • GET /animal_types/ATID: Retrieve an entire Animal Type object

  • GET /employees: Retrieve an entire list of Employees

  • GET /employees/EID: Retreive a specific Employee

  • GET /zoos/ZID/employees: Retrieve a listing of Employees (ID and Name) who work at this Zoo

  • POST /employees: Create a new Employee

  • POST /zoos/ZID/employees: Hire an Employee at a specific Zoo

  • DELETE /zoos/ZID/employees/EID: Fire an Employee from a specific Zoo

在上面的列表裏,ZID表示動物園的ID, AID表示動物的ID,EID表示僱員的ID,還有ATID表示物種的ID。讓文檔裏全部的東西都有一個關鍵字是一個好主意。

爲了簡潔起見,我已經省略了全部API共有的URL前綴。做爲溝通方式這沒什麼問題,可是若是你真要寫到API文檔中,那就必須包含完整的路徑(如,GET http://api.example.com/v1/animal_type/ATID)。

請注意如何展現數據之間的關係,特別是僱員與動物園之間的多對多關係。經過添加一個額外的URL段就能夠實現更多的交互能力。固然沒有一個HTTP動詞能表示正在解僱一我的,可是你可使用DELETE一個動物園裏的僱員來達到相同的效果。

過濾器

當客戶端建立了一個請求來獲取一個對象列表時,很重要一點就是你要返回給他們一個符合查詢條件的全部對象的列表。這個列表可能會很大。但你不能隨意給返回數據的數量作限制。由於這些無謂的限制會致使第三方開發者不知道發生了什麼。若是他們請求一個確切的集合而且要遍歷結果,然而他們發現只拿到了100條數據。接下來他們就不得不去查找這個限制條件的出處。究竟是ORM的bug致使的,仍是由於網絡截斷了大數據包?

儘量減小那些會影響到第三方開發者的無謂限制。

這點很重要,但你可讓客戶端本身對結果作一些具體的過濾或限制。這麼作最重要的一個緣由是能夠最小化網絡傳輸,並讓客戶端儘量快的獲得查詢結果。其次是客戶端可能比較懶,若是這時服務器能對結果作一些過濾或分頁,對你們都是好事。另一個不那麼重要的緣由是(從客戶端角度來講),對服務器來講響應請求的負載越少越好。

過濾器是最有效的方式去處理那些獲取資源集合的請求。因此只要出現GET的請求,就應該經過URL來過濾信息。如下有一些過濾器的例子,多是你想要填加到API中的:

  • ?limit=10: 減小返回給客戶端的結果數量(用於分頁)

  • ?offset=10: 發送一堆信息給客戶端(用於分頁)

  • ?animal_type_id=1: 使用條件匹配來過濾記錄

  • ?sortby=name&order=asc:  對結果按特定屬性進行排序

有些過濾器可能會與端點URL的效果重複。例如我以前提到的GET /zoo/ZID/animals。它也一樣能夠經過GET /animals?zoo_id=ZID來實現。獨立的端點會讓客戶端更好過一些,由於他們的需求每每超出你的預期。本文中提到這種冗餘差別可能對第三方開發者並不可見。

不管怎麼說,當你準備過濾或排序數據時,你必須明確的將那些客戶端能夠過濾或排序的列放到白名單中,由於咱們不想將任何的數據庫錯誤發送給客戶端。

狀態碼

對於一個RESTful API來講很重要的一點就是要使用HTTP的狀態碼,由於它們是HTTP的標準。不少的網絡設備均可以識別這些狀態碼,例如負載均衡器可能會經過配置來避免發送請求到一臺web服務器,若是這臺服務器已經發送了不少的50x錯誤回來。這裏有大量的HTTP狀態碼能夠選擇,可是下面的列表只給出了一些重要的代碼做爲一個參考:

  • 200  OK – [GET]

    客戶端向服務器請求數據,服務器成功找到它們

  • 201  CREATED – [POST/PUT/PATCH]  

    客戶端向服務器提供數據,服務器根據要求建立了一個資源

  • 204  NO CONTENT – [DELETE]  

    客戶端要求服務器刪除一個資源,服務器刪除成功

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

    客戶端向服務器提供了不正確的數據,服務器什麼也沒作

  • 404  NOT FOUND – [*]  

    客戶端引用了一個不存在的資源或集合,服務器什麼也沒作

  • 500  INTERNAL SERVER ERROR – [*]  

    服務器發生內部錯誤,客戶端沒法得知結果,即使請求已經處理成功

狀態碼範圍

1xx範圍的狀態碼是保留給底層HTTP功能使用的,而且估計在你的職業生涯裏面也用不着手動發送這樣一個狀態碼出來。

2xx範圍的狀態碼是保留給成功消息使用的,你儘量的確保服務器總髮送這些狀態碼給用戶。

3xx範圍的狀態碼是保留給重定向用的。大多數的API不會太常使用這類狀態碼,可是在新的超媒體樣式的API中會使用更多一些。

4xx範圍的狀態碼是保留給客戶端錯誤用的。例如,客戶端提供了一些錯誤的數據或請求了不存在的內容。這些請求應該是冪等的,不會改變任何服務器的狀態。

5xx範圍的狀態碼是保留給服務器端錯誤用的。這些錯誤經常是從底層的函數拋出來的,而且開發人員也一般無法處理。發送這類狀態碼的目的是確保客戶端能獲得一些響應。收到5xx響應後,客戶端沒辦法知道服務器端的狀態,因此這類狀態碼是要儘量的避免。

預期的返回文檔

當使用不一樣的HTTP動詞向服務器請求時,客戶端須要在返回結果裏面拿到一系列的信息。下面的列表是很是經典的RESTful API定義:

  • GET /collection: 返回一系列資源對象

  • GET /collection/resource: 返回單獨的資源對象

  • POST /collection: 返回新建立的資源對象

  • PUT /collection/resource: 返回完整的資源對象

  • PATCH /collection/resource: 返回完整的資源對象

  • DELETE /collection/resource: 返回一個空文檔

請注意當一個客戶端建立一個資源時,她們經常不知道新建資源的ID(也許還有其餘的屬性,如建立和修改的時間戳等)。這些屬性將在隨後的請求中返回,而且做爲剛纔POST請求的一個響應結果。

認證

服務器在大多數狀況下是想確切的知道誰建立了什麼請求。固然,有些API是提供給公共用戶(匿名用戶)的,可是大部分時間裏也是表明某人的利益。

OAuth2.0提供了一個很是好的方法去作這件事。在每個請求裏,你能夠明確知道哪一個客戶端建立了請求,哪一個用戶提交了請求,而且提供了一種標準的訪問過時機制或容許用戶從客戶端註銷,全部這些都不須要第三方的客戶端知道用戶的登錄認證信息。

還有OAuth1.0和xAuth一樣適用這樣的場景。不管你選擇哪一個方法,請確保它爲多種不一樣語言/平臺上的庫提供了一些通用的而且設計良好文檔,由於你的用戶可能會使用這些語言和平臺來編寫客戶端。

內容類型

目前,大多數「精彩」的API都爲RESTful接口提供JSON數據。諸如Facebook,Twitter,Github等等你所知的。XML曾經也火過一把(一般在一個大企業級環境下)。這要感謝SOAP,不過它已經掛了,而且咱們也沒看到太多的API把HTML做爲結果返回給客戶端(除非你在構建一個爬蟲程序)。

只要你返回給他們有效的數據格式,開發者就可使用流行的語言和框架進行解析。若是你正在構建一個通用的響應對象,經過使用一個不一樣的序列化器,你也能夠很容易的提供以前所提到的那些數據格式(不包括SOAP)。而你所要作的就是把使用方式放在響應數據的接收頭裏面。

有些API的建立者會推薦把.json, .xml, .html等文件的擴展名放在URL裏面來指示返回內容類型,但我我的並不習慣這麼作。我依然喜歡經過接收頭來指示返回內容類型(這也是HTTP標準的一部分),而且我以爲這麼作也比較適當一些。

超媒體API

超媒體API極可能就是RESTful API設計的未來。超媒體是一個很是棒的概念,它迴歸到了HTTP和HTML如何運做的「本質」。

在非超媒體RESTful API的情景中,URL端點是服務器與客戶端契約的一部分。這些端點必須讓客戶端事先知道,而且修改它們也意味着客戶端可能再也沒法與服務器通訊了。你能夠先假定這是一個限制。

時至今日,英特網上的API客戶端已經不只僅只有那些建立HTTP請求的用戶代理了。大多數HTTP請求是由人們經過瀏覽器產生的。人們不會被哪些預先定義好的RESTful API端點URL所束縛。是什麼讓人們變的如此不同凡響?由於人們能夠閱讀內容,能夠點擊他們感興趣的連接,並瀏覽一下網站,而後跳到他們關注的內容那裏。即便一個URL改變了,人們也不會受到影響(除非他們事先給某個頁面作了書籤,這時他們回到主頁並發現原來有一條新的路徑能夠去往以前的頁面)。

超媒體API概念的運做跟人們的行爲相似。經過請求API的根來得到一個URL的列表,這個列表裏面的每個URL都指向一個集合,而且提供了客戶端能夠理解的信息來描述每個集合。是否爲每個資源提供ID並不重要(或者不是必須的),只要提供URL便可。

一個超媒體API一旦具備了客戶端,那麼它就能夠爬行連接並收集信息,而URL老是在響應中被更新,而且不須要如契約的一部分那樣事先被知曉。若是一個URL曾經被緩存過,而且在隨後的請求中返回404錯誤,那麼客戶端能夠很簡單的回退到根URL並從新發現內容。

在獲取集合中的一個資源列表時會返回一個屬性,這個屬性包含了各個資源的完整URL。當實施一個POST/PATCH/PUT請求後,響應能夠被一個3xx的狀態碼重定向到完整的資源上。

JSON不只告訴了咱們須要定義哪些屬性做爲URL,也告訴了咱們如何將URL與當前文檔關聯的語義。正如你猜的那樣,HTML就提供了這樣的信息。咱們可能很樂意看到咱們的API走完了完整的週期,並回到了處理HTML上來。想一下咱們與CSS一塊兒前行了多遠,有一天咱們可能再次看到它變成了一個通用實踐讓API和網站能夠去使用相同的URL和內容。

文檔

老實說,即便你不能百分之百的遵循指南中的條款,你的API也不是那麼糟糕。可是,若是你不爲API準備文檔的話,沒有人會知道怎麼使用它,那它真的會成爲一個糟糕的API。

讓你的文檔對那些未經認證的開發者也可用

不要使用文檔自動化生成器,即使你用了,你也要保證本身審閱過並讓它具備更好的版式。

不要截斷示例中請求與響應的內容,要展現完整的東西。並在文檔中使用高亮語法。

文檔化每個端點所預期的響應代碼和可能的錯誤消息,和在什麼狀況下會產生這些的錯誤消息

若是你有富餘的時間,那就建立一個控制檯來讓開發者能夠當即體驗一下API的功能。建立一個控制檯並無想象中那麼難,而且開發者們(內部或者第三方)也會所以而擁戴你。

另外確保你的文檔可以被打印。CSS是個強大的工具能夠幫助到你。並且在打印的時候也不用太擔憂邊側欄的問題。即使沒有人會打印到紙上,你也會驚奇的發現不少開發者願意轉化成PDF格式進行離線閱讀。

勘誤:原始的HTTP封包

由於咱們所作的都是基於HTTP協議,因此我將展現給你一個解析了的HTTP封包。我常常很驚訝的發現有多少人不知道這些東西。當客戶端發送一個請求道服務器時,他們會提供一個鍵值對集,先是一個頭,緊跟着是兩個回車換行符,而後纔是請求體。全部這些都是在一個封包裏被髮送。

服務器響應也是一樣的鍵值對集,帶兩個回車換行符,而後是響應體。HTTP就是一個請求/響應協議;它不支持「推送」模式(服務器直接發送數據給客戶端),除非你採用其餘協議,如Websockets。

當你設計API時,你應該可以使用工具去查看原始的HTTP封包。Wireshark是個不錯的選擇。同時,你也該採用一個框架/web服務器,使你可以在必要時修改某些字段的值。

Example HTTP Request

POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24

{
  "name""Gir",
  "animal_type": 12
}

Example HTTP Response

HTTP/1.1 200 OK
Date: Wed, 18 Dec 2013 06:08:22 GMT
Content-Type: application/json
Access-Control-Max-Age: 1728000
Cache-Control: no-cache

{
  "id": 12,
  "created": 1386363036,
  "modified": 1386363036,
  "name""Gir",
  "animal_type": 12
}

來源:http://www.cnblogs.com/moonz-wu/p/4211626.html

相關文章
相關標籤/搜索