目錄
- 定義(Definitions)
- 數據的設計與抽象化(Data Design and Abstraction)
- HTTP動詞 (Verbs)
- 版本(Versioning)
- 分析 (Analytics)
- API根路徑 (API Root URL)
- 路徑 (Endpoint)
- 信息過濾 (Filtering)
- 狀態(tài)碼 (Status Codes)
- 文檔返回值 (Expected Return Documents)
- 身份認證 (Authentication)
- 內容形式 (Content Type)
- 超媒體 (Hypermedia APIs)
- 文檔 (Documentation)
- 其它: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來代表不同的動物園,每個動物園包含很多動物(每個動物只屬于一個動物園),員工(可以在多個動物園里工作)照顧每個動物,那么你建的路徑如下:
- 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動作的路徑。以下列舉了一些可行的動作來代表一個可以運作的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)碼,這個客戶可能都不會收到任何回應,所以這些錯誤也是不可避免的。
- 文檔返回值 (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!