restful api 設計原則

目錄

  1. 定義(Definitions)
  2. 數據的設計與抽象化(Data Design and Abstraction)
  3. HTTP動詞 (Verbs)
  4. 版本(Versioning)
  5. 分析 (Analytics)
  6. API根路徑 (API Root URL)
  7. 路徑 (Endpoint)
  8. 信息過濾 (Filtering)
  9. 狀態(tài)碼 (Status Codes)
  10. 文檔返回值 (Expected Return Documents)
  11. 身份認證 (Authentication)
  12. 內容形式 (Content Type)
  13. 超媒體 (Hypermedia APIs)
  14. 文檔 (Documentation)
  15. 其它:HTTP包文

引言

眾所周知,優(yōu)秀的API設計是相當困難的!API建立起服務器和那些希望調用數據的客戶之間的聯(lián)系。打破了這個約定會導致開發(fā)者收到大量的報怨郵件,大量的客戶因此要修改他們不能繼續(xù)使用的APP。當然API文檔可以有效降低這些問題,但是大多數程序員都不愛寫文檔。

建立起API機制是提高你的服務的重要途徑之一。通過建立API,你的服務器/請求中心會變成其它請求服務的一個平臺。看看那些擁有大數據的公司:Facebook,Twitter,谷歌,GitHub,亞馬遜,Netfilx,無一不是因為他們開放了數據中心的API,使得他們變得很強大。事實上,這個產業(yè)生存的最重要的原因就是那些平臺所提供的消費數據。

越早建立起你的API,越多的人們會使用你的服務。

在你設計API時,及時的更新文檔,這些文檔的設計的目的是為了確保調用你API的客戶能更容易的理解,這樣你可以大幅的減少收到關于困惑或是抱怨的郵件。我將會整理設計原則方面的篇章,你可以根據你的需要讀其中的一段或幾段。

1. 定義 (Definitions)

以下是一些我會在這篇文章中用到的名詞:

  • 資源(Resource): 一個對象的記錄。如:一個動物。
  • 集合(Collection): 很多對象的集合。如:很多動物。
  • HTTP: 互聯(lián)網交互的協(xié)議。
  • 客戶(Consumer): 通過電腦會做出HTTP請求的客戶。
  • 第三方開發(fā)人員(Third Party Developer): 需要獲取服務器數據的第三方開發(fā)人員。
  • 服務器(Server): 一個可以處理客戶請求的HTTP的服務或應用。
  • 路徑(Endpoint): 一個服務器API的路徑,代表了一個資源或一個集合。
2. 數據的設計與抽象化 (Data Design and Abstraction)

設計API比你想象的要簡單。首先你需要考慮如何設計你的數據以及你的服務哭/請求中心將如何工作。如果你在開始寫程序的時候就應該考慮API的設計,這樣會使得一切變得簡單起來。但是如果你想要在已經完成的程序中添加API,你可能需要提供更多的抽象化服務。

這里,我們臨時將一個集合(Collection)看做是一個數據庫表,資源(Resource)看作是數據表中的一條記錄。當然這是一個特殊的例子,事實上,你的API必須將你的數據和業(yè)務邏輯盡可能的剝離開來。這點是非常重要的,這樣可以消除第三方調用API的開發(fā)者的困惑,否則導致的結果就是他們不想使用你的API。

當然,有些重要的服務是不能通過API暴露給第三方用戶的,一個簡單的例子就是很多API不允許第三方使用者調用API來新增用戶。

3. HTTP動詞 (Verbs)

毫無疑問,你肯定知道GET和POST請求。它們是瀏覽器中最常用的請求類型。POST請求非常流行以致于都快成了日常用語。很多對互聯(lián)網的原理一無所知的人們卻可能知道他們可以通過Facebook的友好請求,‘POST‘一些信息上去。

有關于HTTP的動作,你需要掌握四個半。其中的半個是因為‘PATCH‘ 和’PUT‘這個動作很像。對于API開發(fā)者來說,其它四個HTTP動作都是有兩兩聯(lián)系的。下面是具體的作動,以及解釋(在這里我假設大部分的讀者都懂得數據庫方面的知識)。

  • GET(select):從服務器中取出資源。(一個或是多個)
  • POST(create):在服務器上新增一個資源。
  • PUT(update):在服務器上修改資源(需要提供整個資源信息)。
  • PATCH(update):在服務器上修改資源(只需要提供待修改的資源信息)。
  • Delete(delete):從服務器上刪除資源。

以下是兩個比較少用到的HTTP動作:
HEAD:從服務器上取回單個元數據的資源,例如數據的哈希值或數據的最后更新時間等。
OPTIONS:從服務器上取回客戶允許得到的資源信息。

一個好的RESTful API會使用以上“四個半”動作來供第三方使用者獲得或上傳數據。并且URL不會包含任何的ACTION或是其它的動作。

瀏覽器通常會緩存GET請求(大多數如此!),例如一個被緩存了的GET請求(需要看緩存的HEADERS)要比POST請求快一些。一個HEAD請求一般是沒有回應BODY的GET請求,當然也能被緩存。

4. 版本(Versioning)

無論你建立的是怎樣的服務,在開始寫程序前做了大量的設計,只要你的服務器代碼有變動,那么你的數據關系就可能發(fā)生變化,比如你的數據資源可能會新增或刪除一些屬性。這是開發(fā)必不可少的工作,尤其是你的程序已經上線并且被很多人所使用。

請記住API是服務提供方和客戶之間的聯(lián)系。如果你需要改變你的服務器API,這將會破壞兼容性問題。如果你要破壞原有的API,你的客戶將會痛恨你。如果你持續(xù)的修改你的API,你將會失去很多客戶。所以需要你確保你的應用是逐步的更新,這樣才會使得調用你的API的客戶滿意。你需要建立API的版本并且在有需要的時候介紹新的版本給客戶,并且保持老的版本可持續(xù)訪問。

另外一個建議是如果你的API只是簡單的新增一些特性,例如在你的資源中新增一些屬性(這些屬性不是必填項,資源沒有這些屬性也能照常運行),或者你新增一些新的路徑(Endpoints),這個時候你不必更新你的API版本號,因為你所做的這些改變并不會打破現有的兼容性。當然你需要更新你的API文檔。

過段時間后,你可以不建議使用你的API的舊版本。不過不建議使用并不意味著關閉它或降低那個版本的質量,而只是告訴客戶舊版本將會在未來的某一天不能使用,需要他們盡快更新到最新的版本。

好的RESTful API會在URL上顯示版本號。最常見的解決辦法就是把版本號放在請求的頭部中。但經過我跟很多不同的開發(fā)人員的討論,得出的結論是將版本號放在HTTP頭信息中不如放在URL上來的方便和直觀。例:將API的版本號放入URL: https://api.example.com/v1/

5. 分析 (Analytics)

優(yōu)秀的程序會經常追蹤客戶調用你的API的版本/路徑。這個可以在你的數據庫中用整數型字段統(tǒng)計下,當每次請求過來的時候,就增加一次。這樣做有很多優(yōu)點,最常見的好處是我們就可以知道哪個API請求是最高效的。

為了創(chuàng)建第三方開發(fā)人員喜歡的API,最重要的事情是當你不贊成使用舊的API版本時,你需要聯(lián)系那些還在使用舊版本的開發(fā)人員。這可能是你升級并徹底放棄一個API舊版本最好的方法。

通知第三方開發(fā)人員可以是自動的,例如當超過10,000的不贊成使用的請求被調用時,就可以自動發(fā)郵件給那些開發(fā)人員。

6. API根路徑 (API Root URL)

你的API根路徑(域名)的設計非常重要。當一個開發(fā)人員(可看作是作古派風格的開發(fā)者)在一個很老的程序上使用你的API的新的特性時,他們可能不知道你服務器,也許他們可能只是知道一系列的后綴請求的URL。所以盡量簡化你的API根路徑,因為一個復雜的URL會使得一些開發(fā)人員放棄你的API。

以下是兩個常見的根路徑URL:

如果你的應用很大或是你預期你的應用會很大,那么把你的API放到單獨的子域里是個很不錯的選擇,這樣可以使得你的API更加的靈活容易維護。例如:https://api.example.com/v1/*

如果你覺得你的API不是很復雜,或者是你只想要個小應用程序(比如你希望你的網站和API放在同一個框架下),那么就把你的API放在根域名的URL后面。例如:https://example.org/api/v1/*

有個可以存放API路徑列表的頁面是個很好的主意。比如點擊GitHub的API的根路徑就可以得到一個路徑列表。就我個人而言,我是非常喜歡這種點擊根路徑就得到所有API URL或者得到API的開發(fā)文檔的形式,這樣有助于開發(fā)者的開發(fā)。

另外,作為好的RESTful API, API與用戶的通信協(xié)議,總是使用HTTPs協(xié)議。

7. 路徑 (Endpoint)

路徑就是你API的URL,它指出了資源的詳情或是一堆資源的集合。

舉例說明:
如果你想要建立一個虛構的API來代表不同的動物園,每個動物園包含很多動物(每個動物只屬于一個動物園),員工(可以在多個動物園里工作)照顧每個動物,那么你建的路徑如下:

關于每個路徑的作用,你需要列舉出一些HTTP動作的路徑。以下列舉了一些可行的動作來代表一個可以運作的API,值得注意的是我已經把每個HTTP動作放到了路徑前,并寫上了一些注釋。

  • GET /zoos: 列出所有的動物園(返回ID和名稱,沒有其它更詳細的資料。)
  • POST /zoos: 新建一個動物園
  • GET /zoos/ZID: 根據ZID取出一整個動物園的對象
  • PUT /zoos/ZID: 更新一個動物園(傳入全部屬性以及ZID)
  • PATCH /zoos/ZID: 更新一個動物園(傳入要更新的屬性以及ZID)
  • DELETE /zoos/ZID: 刪除一個動物園
  • GET /zoos/ZID/animals: 根據ZID取出動物的列表(返回動物的ID和名稱)
  • GET /animals: 列舉出所有的動物。(返回動物ID和名稱)
  • POST /animals: 新建一個動物。
  • GET /animals/AID: 根據動物的ID取出相應的動物的對象。
  • PUT /animals/AID: 新增一個動物(傳入全部屬性以及AID)
  • PATCH /animals/AID: 新增一個動物(傳入要更新的屬性以及AID)
  • GET /animal_types: 根據傳入的動物園類型獲取動物園的列表(返回動物園ID和名稱)
  • GET /animal_types/ATID: 根據傳入的動物園類型ID獲取一個動物園的信息
  • GET /employees: 獲取所有的員工列表
  • GET /employees/EID: 根據員工的ID獲取員工信息
  • GET /zoos/ZID/employees: 獲取在指定動物園里工作的員工列表(返回員工ID和姓名)
  • POST /employees: 新增一個員工
  • POST /zoos/ZID/employees: 在指定的動物園里雇傭一個員工
  • DELETE /zoos/ZID/employees/EID: 在指定的動畫園里解雇一個員工

上述列表中,ZID表示動物園ID,AID表示動物ID,EID表示員工ID,ATID表示動物類型ID。在你的文檔中標明是簡寫意思一個很好的習慣。

我簡化了以上這些常用的API URL前綴。這個形式有利于開發(fā)時的交流,當然在你正式的API文檔中,你需要補齊全部URL的路徑。(例如:GET http://api.example.com/v1/animal_type/ATID).

值得注意的是關于員工和動物園的數據間,可以有很多不同的展示及聯(lián)系,下面我會列舉一個URL,來說明下它們之間特殊的相互影響。比如當我們想解雇一個員工時,我們沒有一個HTTP動作叫‘FIRE’,但是我們可以通過HTTP動作‘DELETE’來表示員工的解雇,這樣也達到了相同的目的。

8. 信息過濾 (Filtering)

當一個客戶發(fā)來獲取一個對象的列表請求時,服務器所給他的每一個對象是否與請求的標準相匹配是非常重要的。如果這個列表的記錄數量很多。那么很重要的一點就是服務器不能將它們都返回給用戶。不然這么龐大的數據量會使得第三方開發(fā)人員很難去分析獲得的數據。如果他們請求的是一個確定的集合,然后迭代得到結果,那么他們就不會想要再看到另外多出來的那100條數據。否則他們就要花額外的工作量去分析那多出來的數據。問題是他們的程序去要去做一些限制呢?還是任由這樣的事情發(fā)生而不去管呢?

最好的辦法是API提供一些參數,讓第三方開發(fā)人員傳參去限制。

通過傳參的模式,服務器可以為客戶提供一些對結果集的排序等功能。當然最重要的是限定參數的辦法使得網絡負擔變小了,同時客戶也能準確的得到他們想要的數據。此外,客戶也可能是比較懶惰的,如果客戶端能為他們提供過濾或是分頁的數據,那何樂而不為呢?

信息過濾最常見是存在于HTTP動作的GET中,既然他們是GET請求,那么過濾參數就必須寫在URL里。下面是一些你可以參考的關于過濾參數的例子:

  • ?limit=10: 指定返回記錄的數量 (有分頁)
  • ?offset=10: 返回記錄的開始位置 (有分頁)
  • ?animal_type_id=1: 指定篩選條件。(例如WHERE animal_type_id = 1)
  • ?sortby=name&order=asc: 指定返回結果按照哪個屬性排序,以及排序順序。 (例如ORDER BY name ASC)

對于API的路徑和URL的參數允許存在冗余(即重復)。比如我之前的例子 GET/zoo/ZID/animals和GET /animals?zoo_id=ZID的含義就是相同的。有些專用的路徑會方便客戶的調用,特別是那些你預計他們會經常使用的路徑。在你的API文檔中,需要寫清楚哪些是冗余的,這樣可以消除第三方開發(fā)人員的疑惑。

另外還要提一下,當參數可以指定按照某一屬性進行排序時,你需要指出哪些屬性是可以排序的,即需要整理出可以進行排序的屬性的白名單,因為我們不需要發(fā)送錯誤的數據給客戶。

9. 狀態(tài)碼 (Status Codes)

作為一個RESTful的API,你需要正確使用HTTP的響應碼。他們是一套標準,許多網絡技術、設備都能讀懂。例如負載均衡(load balancers)就能分辯出這些狀態(tài)碼并且避免了當錯誤為50開頭時發(fā)送給瀏覽器的很多回應。HTTP有很多很狀態(tài)碼,查看全部請點擊這里,下面我簡單列了一些重要的狀態(tài)碼:

  • 200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
  • 204 NO CONTENT - [DELETE]:用戶刪除數據成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。
  • 404 NOT FOUND -
    :用戶發(fā)出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。
  • 500 INTERNAL SERVER ERROR - [*]:服務器發(fā)生錯誤,用戶將無法判斷發(fā)出的請求是否成功。

狀態(tài)碼邊界:

1XX狀態(tài)碼是HTTP比較低等級的狀態(tài)碼,在HTTP/1.0協(xié)議中沒有定議任何1XX狀態(tài)碼,所以最好不要向客戶發(fā)送任何1XX的狀態(tài)碼。
2XX狀態(tài)碼說明了客戶端接收了正確的消息。這種狀態(tài)碼是極好的,所以確保你的服務器發(fā)送的都是這類的狀態(tài)碼。
3XX狀態(tài)碼用來重定向。很多API通常不會使用這些請求(SEO優(yōu)化人員會使用它們)。值得一提的是,新出來的Hypermedia API還是會使用3XX的狀態(tài)碼的。
4XX狀態(tài)碼表示客戶端的請求錯誤。比如客戶端提供了錯誤的參數或請求要獲取的不存在的對象。對于客戶端和服務器端來說這些請求是等冪的。
5XX狀態(tài)碼一般是服務器在處理請求過程中的異常。一般來說這些錯誤被低等級的方法所拋出,當然也不排除開發(fā)人員手動發(fā)送,來確保某個客戶得到某種回應。當服務器發(fā)送了5XX狀態(tài)碼,這個客戶可能都不會收到任何回應,所以這些錯誤也是不可避免的。

  1. 文檔返回值 (Expected Return Documents)

當使用不同的HTTP動作來獲取資源時,一個用戶需要獲取一些有排序的數據,以下列舉了典型的RESTful APIs:

  • GET /collection: 返回一個資源集合。
  • GET /collection/resource: 返回單個資源對象。
  • POST /collection: 返回最新創(chuàng)建的資源對象。
  • PUT /collection/resource: 返回更新完畢的資源對象。
  • PATCH /collection/resource: 返回更新完畢的資源對象。
  • DELETE /collection/resource: 返回一個空的文檔。

當客戶新增一個資源時,他們通常不知道新增的資源的ID(或最新更新的時間等屬性),這些額外的屬性需要通過POST同時返回給客戶。

11. 身份認證 (Authentication)

大多數情況下服務器會對請求方的身份進行驗證。當然有些API提供了可以被普通用戶調用的路徑,但多數的API都需要認證的過程。

OAuth2.0提供了一個很好的解決方案。在每次請求時,服務器需要知道哪個第三方程序發(fā)來想要獲取系統(tǒng)的某個用戶信息的請求,這時候,就需要用戶授權給那個第三方程序,允許它獲得用戶的信息。

除此之外,還有OAuth 1.0 和 xAuth。無論你用的是哪種認證技術,你都需要確保你所使用的是常見技術,然后編輯好文 檔和供不同平臺使用的類庫。

就我的經驗來看,關于以安全著稱的OAuth1.0a,對它對接接口是非常痛苦的事。我最近驚奇的發(fā)現由于它沒有提供很多語言的類型,很多的第三方開發(fā)人員都開始擁有了自己的類庫。我曾經為了它用了好幾個小時來調試一個叫“無效簽名”的錯誤,這真是太糟糕了。

12. 內容形式 (Content Type)

關于服務器返回的數據格式,應該盡量使用JSON格式。包括Facebook, Twitter, GitHub都是使用了這種格式。其它的格式還有XML、SOAP。但是XML格式容易引起很多糾紛(特別是大型交互環(huán)境)。SOAP在當今幾乎沒人用了。

開發(fā)人員使用流行的語言我框架可以對數據進行有效的轉換。如果服務器想提供一個很普通的回應對象,那么它可以很容易的提供上述的數據格式(不包括SOAP)。值得注意的是,你需要正確使用響應文檔中的頭部內容。

一些API創(chuàng)建者在響應內容中提供了響應格式的參數,包括json\xml\html。雖然本人很少這樣干,但仍然覺得這是個不錯的形式。

13. 超媒體 (Hypermedia APIs)

Hypermedia APIs是RESTful API設計的發(fā)展趨勢,即在返回結果中提供鏈接。這樣可以使用戶在不查API文 檔的前提下,也可能知道下一步應該要做什么。

當使用一個不是Hypermedia的RESTful API時,路徑是服務器和客戶之間的聯(lián)系。但這些路徑必須是被客戶知道的,一旦服務器改變了這些路徑的作用,那么客戶將不能跟服務器進行通信,這就是傳統(tǒng)RESTful API的一大限制。

對于現在來說,API客戶不再是發(fā)起HTTP的唯一請求方,用著瀏覽器的人們都是發(fā)起HTTP的請求方。但是這些人并不會局限于使用預先定定好的RESTful API。這是為什么呢?因為他們讀的是真正的內容,他們點擊一些想要讀的標題,然后一個網頁就會打開來了,他們就可以知道他們真正想要的內容。如果一個URL改變了,網友們是不會受影響的(除非他們定閱了一個頁面,而不是在首頁是新打開一個標題去閱讀)

Hypermedia API的概念跟現實人類的世界很像。請求API的根目錄得到可以請求每個集合信息以及描述這些集合的用途的URL列表。另外,提供給每個資原的ID不是必須的,因為URL里已經提供。

當一個用戶使用Hypemedia風格的API的鏈接來收集信息時,響應里一般都會包含最新的URL,這些URL沒必要提前寫在文檔里。如果一個URL被緩存了,那么可能就會返回404錯誤,客戶可以很容易的回到根目錄來找到正確的URL。

當要獲取一個資源列表的集合,可以在響應的資源里放上一個關于URL的屬性。當需要調用POST/PATCH/PUT,服務器回應的時候可以直接用3XX的響應碼來做重定向。

14. 文檔 (Documentation)

說實話,如果你不是100%確定你的標準,那么你的API也不是最糟糕的。但如果你的API沒有文 檔,這將會是很糟糕的API,那么沒有人將會使用你的API。

所以撰寫API文檔是十分有必要的。

不要使用自動生成的文檔,但如果你真的這么做了,那一定要保證你檢查和校驗過這個文檔。
不要縮減請求和響應的內容,最好就全部展求。在你的文檔中有些重點的要加粗。

在文檔中,要加上每個路徑的響應的內容和可能出現的信息錯誤,并盡可能的寫上在什么情況下可能導致這些錯誤。

如果你有時間的話,就建立一個開發(fā)者API的終端機,這樣開發(fā)者就可以更方便 的體驗你的API。這對你來說其實并不困難,并且所有的開發(fā)者(你公司內部的或第三方開發(fā)者)都會很喜歡這種形式。

必須得確保你的文檔可以被打印。CSS是個很強大的技術。當要打印的時候不必要隱藏你的側邊欄,因為很多開發(fā)者都喜歡離線復印去閱讀API的文檔。

15. 其它:HTTP包文

既然我們所做的所有事都是基于HTTP,那么我們接下來要討論下HTTP包文。如果一個從事這個工作的人不知道HTTP包文,那是很糟糕的事情。當一個客戶發(fā)送給服務器發(fā)送請求,會提供一個包含鍵/值(Key/Value)的集合,叫頭部(Header),再過幾行就是請求的內容(Body)了。頭部和內容是放在同一個包里進行發(fā)送的。

服務器的響應格式也是一對對的鍵/值(Key/Value)形式,HTTP是一個請求/響應的協(xié)議,它不支持服務器無緣無故的主動推送給客戶端,除非你使用另一個叫Websockets的協(xié)議。
當設計API時,一般需要用一些工具來幫助你看HTTP的包。比如Wireshark,當然你也可以用一些框架/WEB的服務自帶的工具來確保
轉載(http://angelbill3.iteye.com/blog/2108785
原文(https://codeplanet.io/principles-good-restful-api-design/
關注一波!喜歡一波!本人是前端小白,這是我的個人博客鄧鵬的博客, 使用的技術 vue全家桶 + koa2 + mysql + php + nginx!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容