轉(zhuǎn)載自 http://www.cnblogs.com/moonz-wu/p/4211626.html
做出一個(gè)好的API設(shè)計(jì)很難。API表達(dá)的是你的數(shù)據(jù)和你的數(shù)據(jù)使用者之間的契約套鹅。打破這個(gè)契約將會(huì)招致很多憤怒的郵件汰具,和一大堆傷心的用戶-因?yàn)樗麄兪謾C(jī)上的App不工作了留荔。而文檔化只能達(dá)到一半的效果,并且也很難找到一個(gè)愿意寫文檔的程序員杰妓。
你所能做的最重要一件事來(lái)提高服務(wù)的價(jià)值就是創(chuàng)建一個(gè)API碘勉。因?yàn)殡S著其他服務(wù)的成長(zhǎng),有這樣一個(gè)API會(huì)使你的服務(wù)或者核心應(yīng)用將有機(jī)會(huì)變成一個(gè)平臺(tái)倍宾。環(huán)顧一下現(xiàn)有的這些大公司:Facebook高职,Twitter辞州,Google, Github埃元,Amazon牵啦,Netflix等哈雏。如果當(dāng)時(shí)他們沒(méi)有通過(guò)API來(lái)開放數(shù)據(jù)的話,也不可能成長(zhǎng)到如今的規(guī)模土浸。事實(shí)上彭羹,整個(gè)行業(yè)存在的唯一目的就是消費(fèi)所謂平臺(tái)上的數(shù)據(jù)。
你的API越容易使用还最,那么就會(huì)有越多的人去用它
本文提到的這些原則拓轻,如果你的API能嚴(yán)格按照這些原則來(lái)設(shè)計(jì),使用者就可以知道它接下來(lái)要做什么勿锅,并且能減少大量不必要的疑惑或者是憤怒的郵件枣氧。我已經(jīng)把所有內(nèi)容都整理到不同的主題里了,你無(wú)需按順序去閱讀它张弛。
定義
這里有一些非常重要的術(shù)語(yǔ)宗挥,我將在本文里面一直用到它們:
資源:一個(gè)對(duì)象的單獨(dú)實(shí)例丹喻,如一只動(dòng)物
集合:一群同種對(duì)象俊性,如動(dòng)物
HTTP:跨網(wǎng)絡(luò)的通信協(xié)議
客戶端:可以創(chuàng)建HTTP請(qǐng)求的客戶端應(yīng)用程序
第三方開發(fā)者:這個(gè)開發(fā)者不屬于你的項(xiàng)目但是有想使用你的數(shù)據(jù)
服務(wù)器:一個(gè)HTTP服務(wù)器或者應(yīng)用程序,客戶端可以跨網(wǎng)絡(luò)訪問(wèn)它
端點(diǎn):這個(gè)API在服務(wù)器上的URL用于表達(dá)一個(gè)資源或者一個(gè)集合
冪等:無(wú)邊際效應(yīng)踢械,多次操作得到相同的結(jié)果
URL段:在URL里面已斜杠分隔的內(nèi)容
數(shù)據(jù)設(shè)計(jì)與抽象
規(guī)劃好你的API的外觀要先于開發(fā)它實(shí)際的功能内列。首先你要知道數(shù)據(jù)該如何設(shè)計(jì)和核心服務(wù)/應(yīng)用程序會(huì)如何工作。如果你純粹新開發(fā)一個(gè)API嫩与,這樣會(huì)比較容易一些交排。但如果你是往已有的項(xiàng)目中增加API埃篓,你可能需要提供更多的抽象。
有時(shí)候一個(gè)集合可以表達(dá)一個(gè)數(shù)據(jù)庫(kù)表同窘,而一個(gè)資源可以表達(dá)成里面的一行記錄,但是這并不是常態(tài)桨仿。事實(shí)上案狠,你的API應(yīng)該盡可能通過(guò)抽象來(lái)分離數(shù)據(jù)與業(yè)務(wù)邏輯骂铁。這點(diǎn)非常重要罩抗,只有這樣做你才不會(huì)打擊到那些擁有復(fù)雜業(yè)務(wù)的第三方開發(fā)者套蒂,否則他們是不會(huì)使用你的API的。
當(dāng)然你的服務(wù)可能很多部分是不應(yīng)該通過(guò)API暴露出去的烁挟。比較常見的例子就是很多API是不允許第三方來(lái)創(chuàng)建用戶的骨坑。
動(dòng)詞
顯然你了解GET和POST請(qǐng)求。當(dāng)你用瀏覽器去訪問(wèn)不同頁(yè)面的時(shí)候且警,這兩個(gè)是最常見的請(qǐng)求斑芜。POST術(shù)語(yǔ)如此流行以至于開始侵?jǐn)_通俗用語(yǔ)祟霍。即使是那些不知道互聯(lián)網(wǎng)如何工作的人們也能“post”一些東西到朋友的Facebook墻上。
這里至少有四個(gè)半非常重要的HTTP動(dòng)詞需要你知道大州。我之所以說(shuō)“半個(gè)”的意思是PATCH這個(gè)動(dòng)詞非常類似于PUT厦画,并且它們倆也常常被開發(fā)者綁定到同一個(gè)API上。
GET (選擇):從服務(wù)器上獲取一個(gè)具體的資源或者一個(gè)資源列表力试。
POST (創(chuàng)建): 在服務(wù)器上創(chuàng)建一個(gè)新的資源排嫌。
PUT (更新):以整體的方式更新服務(wù)器上的一個(gè)資源淳地。
PATCH (更新):只更新服務(wù)器上一個(gè)資源的一個(gè)屬性。
DELETE (刪除):刪除服務(wù)器上的一個(gè)資源伍伤。
還有兩個(gè)不常用的HTTP動(dòng)詞:
HEAD : 獲取一個(gè)資源的元數(shù)據(jù)遣钳,如數(shù)據(jù)的哈希值或最后的更新時(shí)間蕴茴。
OPTIONS:獲取客戶端能對(duì)資源做什么操作的信息。
一個(gè)好的RESTful API只允許第三方調(diào)用者使用這四個(gè)半HTTP動(dòng)詞進(jìn)行數(shù)據(jù)交互蒋畜,并且在URL段里面不出現(xiàn)任何其他的動(dòng)詞晃听。
一般來(lái)說(shuō)能扒,GET請(qǐng)求可以被瀏覽器緩存(通常也是這樣的)。例如辛润,緩存請(qǐng)求頭用于第二次用戶的POST請(qǐng)求见秤。HEAD請(qǐng)求是基于一個(gè)無(wú)響應(yīng)體的GET請(qǐng)求鹃答,并且也可以被緩存的。
版本化
無(wú)論你正在構(gòu)建什么置济,無(wú)論你在入手前做了多少計(jì)劃,你核心的應(yīng)用總會(huì)發(fā)生變化护盈,數(shù)據(jù)關(guān)系也會(huì)變化羞酗,資源上的屬性也會(huì)被增加或刪除檀轨。只要你的項(xiàng)目還活著,并且有大量的用戶在用撤师,這種情況總是會(huì)發(fā)生拧揽。
請(qǐng)謹(jǐn)記一點(diǎn)淤袜,API是服務(wù)器與客戶端之間的一個(gè)公共契約衰伯。如果你對(duì)服務(wù)器上的API做了一個(gè)更改意鲸,并且這些更改無(wú)法向后兼容,那么你就打破了這個(gè)契約读慎,客戶端又會(huì)要求你重新支持它槐雾。為了避免這樣的事情,你既要確保應(yīng)用程序逐步的演變株灸,又要讓客戶端滿意慌烧。那么你必須在引入新版本API的同時(shí)保持舊版本API仍然可用鸠儿。
注:如果你只是簡(jiǎn)單的增加一個(gè)新的特性到API上,如資源上的一個(gè)新屬性或者增加一個(gè)新的端點(diǎn)淑翼,你不需要增加API的版本玄括。因?yàn)檫@些并不會(huì)造成向后兼容性的問(wèn)題,你只需要修改文檔即可胃惜。
隨著時(shí)間的推移船殉,你可能聲明不再支持某些舊版本的API斯嚎。申明不支持一個(gè)特性并不意味著關(guān)閉或者破壞它堡僻。而是告訴客戶端舊版本的API將在某個(gè)特定的時(shí)間被刪除,并且建議他們使用新版本的API硼讽。
一個(gè)好的RESTful API會(huì)在URL中包含版本信息牲阁。另一種比較常見的方案是在請(qǐng)求頭里面保持版本信息城菊。但是跟很多不同的第三方開發(fā)者一起工作后,我可以很明確的告訴你赚爵,在請(qǐng)求頭里面包含版本信息遠(yuǎn)沒(méi)有放在URL里面來(lái)的容易冀膝。
分析
所謂API分析就是持續(xù)跟蹤那些正為人使用的API的版本和端點(diǎn)信息霎挟。而這可能就跟每次請(qǐng)求都往數(shù)據(jù)庫(kù)增加一個(gè)整數(shù)那樣簡(jiǎn)單。有很多的原因顯示API跟蹤分析是一個(gè)好主意赐纱,例如疙描,對(duì)那些使用最廣泛的API來(lái)說(shuō)效率是最重要的。
第三方開發(fā)者通常會(huì)關(guān)注API的構(gòu)建目的久又,其中最重要的一個(gè)目的是你決定什么時(shí)候不再支持某個(gè)版本地消。你需要明確的告知開發(fā)者他們正在使用那些即將被移除的API特性畏妖。這是一個(gè)很好的方式在你準(zhǔn)備刪除舊的API之前去提醒他們進(jìn)行升級(jí)戒劫。
當(dāng)然第三方開發(fā)者的通知流程可以以某種條件被自動(dòng)觸發(fā),例如每當(dāng)一個(gè)過(guò)時(shí)的特性上發(fā)生10000次請(qǐng)求時(shí)就發(fā)郵件通知開發(fā)者玻熙。
API根URL
無(wú)論你信不信疯攒,API的根地址很重要敬尺。當(dāng)一個(gè)開發(fā)者接手了一個(gè)舊項(xiàng)目(如進(jìn)行代碼考古時(shí))砂吞。而這個(gè)項(xiàng)目正在使用你的API崎溃,同時(shí)開發(fā)者還想構(gòu)建一個(gè)新的特性袁串,但他們完全不知道你的服務(wù)。幸運(yùn)的是他們知道客戶端對(duì)外調(diào)用的那些URL列表赎瑰。讓你的API根入口點(diǎn)保持盡可能的簡(jiǎn)單是很重要的破镰,因?yàn)殚_發(fā)者很可能一看到那些冗長(zhǎng)而又復(fù)雜的URL就轉(zhuǎn)身而走。
這里有兩個(gè)常見的URL根例子:
https://example.org/api/v1/*
https://api.example.com/v1/*
如果你的應(yīng)用很龐大或者你預(yù)期它將會(huì)變的很龐大集惋,那么將API放到子域下通常是一個(gè)好選擇踩娘。這種做法可以保持某些規(guī)陌运牵化上的靈活性。
但如果你覺(jué)得你的API不會(huì)變的很龐大习寸,或是你只是想讓應(yīng)用安裝更簡(jiǎn)單些(如你想用相同的框架來(lái)支持站點(diǎn)和API)霞溪,將你的API放到根域名下也是可以的。
讓API根擁有一些內(nèi)容通常也是個(gè)好主意中捆。Github的API根就是一個(gè)典型的例子鸯匹。從個(gè)人角度來(lái)說(shuō)我是一個(gè)通過(guò)根URL發(fā)布信息的粉絲,這對(duì)很多人來(lái)說(shuō)是有用的泄伪,例如如何獲取API相關(guān)的開發(fā)文檔殴蓬。
同樣也請(qǐng)注意HTTPS前綴,一個(gè)好的RESTful API總是基于HTTPS來(lái)發(fā)布的蟋滴。
端點(diǎn)
一個(gè)端點(diǎn)就是指向特定資源或資源集合的URL染厅。
如果你正在構(gòu)建一個(gè)虛構(gòu)的API來(lái)展現(xiàn)幾個(gè)不同的動(dòng)物園津函,每一個(gè)動(dòng)物園又包含很多動(dòng)物肖粮,員工和每個(gè)動(dòng)物的物種,你可能會(huì)有如下的端點(diǎn)信息:
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
針對(duì)每一個(gè)端點(diǎn)來(lái)說(shuō)尔苦,你可能想列出所有可行的HTTP動(dòng)詞和端點(diǎn)的組合涩馆。如下所示,請(qǐng)注意我把HTTP動(dòng)詞都放在了虛構(gòu)的API之前允坚,正如將同樣的注解放在每一個(gè)HTTP請(qǐng)求頭里一樣魂那。
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表示動(dòng)物園的ID稠项, AID表示動(dòng)物的ID冰寻,EID表示雇員的ID,還有ATID表示物種的ID皿渗。讓文檔里所有的東西都有一個(gè)關(guān)鍵字是一個(gè)好主意斩芭。
為了簡(jiǎn)潔起見轻腺,我已經(jīng)省略了所有API共有的URL前綴。作為溝通方式這沒(méi)什么問(wèn)題划乖,但是如果你真要寫到API文檔中贬养,那就必須包含完整的路徑(如,GET http://api.example.com/v1/animal_type/ATID)琴庵。
請(qǐng)注意如何展示數(shù)據(jù)之間的關(guān)系误算,特別是雇員與動(dòng)物園之間的多對(duì)多關(guān)系。通過(guò)添加一個(gè)額外的URL段就可以實(shí)現(xiàn)更多的交互能力迷殿。當(dāng)然沒(méi)有一個(gè)HTTP動(dòng)詞能表示正在解雇一個(gè)人儿礼,但是你可以使用DELETE一個(gè)動(dòng)物園里的雇員來(lái)達(dá)到相同的效果。
過(guò)濾器
當(dāng)客戶端創(chuàng)建了一個(gè)請(qǐng)求來(lái)獲取一個(gè)對(duì)象列表時(shí)庆寺,很重要一點(diǎn)就是你要返回給他們一個(gè)符合查詢條件的所有對(duì)象的列表蚊夫。這個(gè)列表可能會(huì)很大。但你不能隨意給返回?cái)?shù)據(jù)的數(shù)量做限制懦尝。因?yàn)檫@些無(wú)謂的限制會(huì)導(dǎo)致第三方開發(fā)者不知道發(fā)生了什么知纷。如果他們請(qǐng)求一個(gè)確切的集合并且要遍歷結(jié)果,然而他們發(fā)現(xiàn)只拿到了100條數(shù)據(jù)陵霉。接下來(lái)他們就不得不去查找這個(gè)限制條件的出處琅轧。到底是ORM的bug導(dǎo)致的,還是因?yàn)榫W(wǎng)絡(luò)截?cái)嗔舜髷?shù)據(jù)包踊挠?
盡可能減少那些會(huì)影響到第三方開發(fā)者的無(wú)謂限制
這點(diǎn)很重要乍桂,但你可以讓客戶端自己對(duì)結(jié)果做一些具體的過(guò)濾或限制。這么做最重要的一個(gè)原因是可以最小化網(wǎng)絡(luò)傳輸效床,并讓客戶端盡可能快的得到查詢結(jié)果模蜡。其次是客戶端可能比較懶,如果這時(shí)服務(wù)器能對(duì)結(jié)果做一些過(guò)濾或分頁(yè)扁凛,對(duì)大家都是好事。另外一個(gè)不那么重要的原因是(從客戶端角度來(lái)說(shuō))闯传,對(duì)服務(wù)器來(lái)說(shuō)響應(yīng)請(qǐng)求的負(fù)載越少越好谨朝。
過(guò)濾器是最有效的方式去處理那些獲取資源集合的請(qǐng)求。所以只要出現(xiàn)GET的請(qǐng)求甥绿,就應(yīng)該通過(guò)URL來(lái)過(guò)濾信息字币。以下有一些過(guò)濾器的例子,可能是你想要填加到API中的:
?limit=10: 減少返回給客戶端的結(jié)果數(shù)量(用于分頁(yè))
?offset=10: 發(fā)送一堆信息給客戶端(用于分頁(yè))
?animal_type_id=1: 使用條件匹配來(lái)過(guò)濾記錄
?sortby=name&order=asc: ?對(duì)結(jié)果按特定屬性進(jìn)行排序
有些過(guò)濾器可能會(huì)與端點(diǎn)URL的效果重復(fù)共缕。例如我之前提到的GET /zoo/ZID/animals洗出。它也同樣可以通過(guò)GET /animals?zoo_id=ZID來(lái)實(shí)現(xiàn)。獨(dú)立的端點(diǎn)會(huì)讓客戶端更好過(guò)一些图谷,因?yàn)樗麄兊男枨笸瞿愕念A(yù)期翩活。本文中提到這種冗余差異可能對(duì)第三方開發(fā)者并不可見阱洪。
無(wú)論怎么說(shuō),當(dāng)你準(zhǔn)備過(guò)濾或排序數(shù)據(jù)時(shí)菠镇,你必須明確的將那些客戶端可以過(guò)濾或排序的列放到白名單中冗荸,因?yàn)槲覀儾幌雽⑷魏蔚臄?shù)據(jù)庫(kù)錯(cuò)誤發(fā)送給客戶端。
狀態(tài)碼
對(duì)于一個(gè)RESTful API來(lái)說(shuō)很重要的一點(diǎn)就是要使用HTTP的狀態(tài)碼利耍,因?yàn)樗鼈兪荋TTP的標(biāo)準(zhǔn)蚌本。很多的網(wǎng)絡(luò)設(shè)備都可以識(shí)別這些狀態(tài)碼,例如負(fù)載均衡器可能會(huì)通過(guò)配置來(lái)避免發(fā)送請(qǐng)求到一臺(tái)web服務(wù)器隘梨,如果這臺(tái)服務(wù)器已經(jīng)發(fā)送了很多的50x錯(cuò)誤回來(lái)程癌。這里有大量的HTTP狀態(tài)碼可以選擇,但是下面的列表只給出了一些重要的代碼作為一個(gè)參考:
200 ?OK – [GET] ?客戶端向服務(wù)器請(qǐng)求數(shù)據(jù)轴猎,服務(wù)器成功找到它們
201 ?CREATED – [POST/PUT/PATCH] ?客戶端向服務(wù)器提供數(shù)據(jù)嵌莉,服務(wù)器根據(jù)要求創(chuàng)建了一個(gè)資源
204 ?NO CONTENT – [DELETE] ?客戶端要求服務(wù)器刪除一個(gè)資源,服務(wù)器刪除成功
400 ?INVALID REQUEST – [POST/PUT/PATCH] ?客戶端向服務(wù)器提供了不正確的數(shù)據(jù)税稼,服務(wù)器什么也沒(méi)做
404 ?NOT FOUND – [*] ?客戶端引用了一個(gè)不存在的資源或集合烦秩,服務(wù)器什么也沒(méi)做
500 ?INTERNAL SERVER ERROR – [*] ?服務(wù)器發(fā)生內(nèi)部錯(cuò)誤,客戶端無(wú)法得知結(jié)果郎仆,即便請(qǐng)求已經(jīng)處理成功
狀態(tài)碼范圍
1xx范圍的狀態(tài)碼是保留給底層HTTP功能使用的只祠,并且估計(jì)在你的職業(yè)生涯里面也用不著手動(dòng)發(fā)送這樣一個(gè)狀態(tài)碼出來(lái)。
2xx范圍的狀態(tài)碼是保留給成功消息使用的扰肌,你盡可能的確保服務(wù)器總發(fā)送這些狀態(tài)碼給用戶抛寝。
3xx范圍的狀態(tài)碼是保留給重定向用的。大多數(shù)的API不會(huì)太常使用這類狀態(tài)碼曙旭,但是在新的超媒體樣式的API中會(huì)使用更多一些盗舰。
4xx范圍的狀態(tài)碼是保留給客戶端錯(cuò)誤用的。例如桂躏,客戶端提供了一些錯(cuò)誤的數(shù)據(jù)或請(qǐng)求了不存在的內(nèi)容钻趋。這些請(qǐng)求應(yīng)該是冪等的,不會(huì)改變?nèi)魏畏?wù)器的狀態(tài)剂习。
5xx范圍的狀態(tài)碼是保留給服務(wù)器端錯(cuò)誤用的蛮位。這些錯(cuò)誤常常是從底層的函數(shù)拋出來(lái)的,并且開發(fā)人員也通常沒(méi)法處理鳞绕。發(fā)送這類狀態(tài)碼的目的是確笔剩客戶端能得到一些響應(yīng)。收到5xx響應(yīng)后们何,客戶端沒(méi)辦法知道服務(wù)器端的狀態(tài)萄焦,所以這類狀態(tài)碼是要盡可能的避免。
預(yù)期的返回文檔
當(dāng)使用不同的HTTP動(dòng)詞向服務(wù)器請(qǐng)求時(shí)冤竹,客戶端需要在返回結(jié)果里面拿到一系列的信息拂封。下面的列表是非常經(jīng)典的RESTful API定義:
GET /collection: 返回一系列資源對(duì)象
GET /collection/resource: 返回單獨(dú)的資源對(duì)象
POST /collection: 返回新創(chuàng)建的資源對(duì)象
PUT /collection/resource: 返回完整的資源對(duì)象
PATCH /collection/resource: 返回完整的資源對(duì)象
DELETE /collection/resource: 返回一個(gè)空文檔
請(qǐng)注意當(dāng)一個(gè)客戶端創(chuàng)建一個(gè)資源時(shí)茬射,她們常常不知道新建資源的ID(也許還有其他的屬性,如創(chuàng)建和修改的時(shí)間戳等)烘苹。這些屬性將在隨后的請(qǐng)求中返回躲株,并且作為剛才POST請(qǐng)求的一個(gè)響應(yīng)結(jié)果。
認(rèn)證
服務(wù)器在大多數(shù)情況下是想確切的知道誰(shuí)創(chuàng)建了什么請(qǐng)求镣衡。當(dāng)然霜定,有些API是提供給公共用戶(匿名用戶)的,但是大部分時(shí)間里也是代表某人的利益廊鸥。
OAuth2.0提供了一個(gè)非常好的方法去做這件事望浩。在每一個(gè)請(qǐng)求里,你可以明確知道哪個(gè)客戶端創(chuàng)建了請(qǐng)求惰说,哪個(gè)用戶提交了請(qǐng)求磨德,并且提供了一種標(biāo)準(zhǔn)的訪問(wèn)過(guò)期機(jī)制或允許用戶從客戶端注銷,所有這些都不需要第三方的客戶端知道用戶的登陸認(rèn)證信息吆视。
還有OAuth1.0和xAuth同樣適用這樣的場(chǎng)景典挑。無(wú)論你選擇哪個(gè)方法,請(qǐng)確保它為多種不同語(yǔ)言/平臺(tái)上的庫(kù)提供了一些通用的并且設(shè)計(jì)良好文檔啦吧,因?yàn)槟愕挠脩艨赡軙?huì)使用這些語(yǔ)言和平臺(tái)來(lái)編寫客戶端您觉。
內(nèi)容類型
目前,大多數(shù)“精彩”的API都為RESTful接口提供JSON數(shù)據(jù)授滓。諸如Facebook琳水,Twitter,Github等等你所知的般堆。XML曾經(jīng)也火過(guò)一把(通常在一個(gè)大企業(yè)級(jí)環(huán)境下)在孝。這要感謝SOAP,不過(guò)它已經(jīng)掛了淮摔,并且我們也沒(méi)看到太多的API把HTML作為結(jié)果返回給客戶端(除非你在構(gòu)建一個(gè)爬蟲程序)私沮。
只要你返回給他們有效的數(shù)據(jù)格式,開發(fā)者就可以使用流行的語(yǔ)言和框架進(jìn)行解析和橙。如果你正在構(gòu)建一個(gè)通用的響應(yīng)對(duì)象仔燕,通過(guò)使用一個(gè)不同的序列化器,你也可以很容易的提供之前所提到的那些數(shù)據(jù)格式(不包括SOAP)胃碾。而你所要做的就是把使用方式放在響應(yīng)數(shù)據(jù)的接收頭里面。
有些API的創(chuàng)建者會(huì)推薦把.json, .xml, .html等文件的擴(kuò)展名放在URL里面來(lái)指示返回內(nèi)容類型筋搏,但我個(gè)人并不習(xí)慣這么做仆百。我依然喜歡通過(guò)接收頭來(lái)指示返回內(nèi)容類型(這也是HTTP標(biāo)準(zhǔn)的一部分),并且我覺(jué)得這么做也比較適當(dāng)一些奔脐。
超媒體API
超媒體API很可能就是RESTful API設(shè)計(jì)的將來(lái)俄周。超媒體是一個(gè)非常棒的概念吁讨,它回歸到了HTTP和HTML如何運(yùn)作的“本質(zhì)”。
在非超媒體RESTful API的情景中峦朗,URL端點(diǎn)是服務(wù)器與客戶端契約的一部分建丧。這些端點(diǎn)必須讓客戶端事先知道,并且修改它們也意味著客戶端可能再也無(wú)法與服務(wù)器通信了波势。你可以先假定這是一個(gè)限制翎朱。
時(shí)至今日,英特網(wǎng)上的API客戶端已經(jīng)不僅僅只有那些創(chuàng)建HTTP請(qǐng)求的用戶代理了尺铣。大多數(shù)HTTP請(qǐng)求是由人們通過(guò)瀏覽器產(chǎn)生的拴曲。人們不會(huì)被哪些預(yù)先定義好的RESTful API端點(diǎn)URL所束縛。是什么讓人們變的如此與眾不同凛忿?因?yàn)槿藗兛梢蚤喿x內(nèi)容澈灼,可以點(diǎn)擊他們感興趣的鏈接,并瀏覽一下網(wǎng)站店溢,然后跳到他們關(guān)注的內(nèi)容那里叁熔。即使一個(gè)URL改變了,人們也不會(huì)受到影響(除非他們事先給某個(gè)頁(yè)面做了書簽床牧,這時(shí)他們回到主頁(yè)并發(fā)現(xiàn)原來(lái)有一條新的路徑可以去往之前的頁(yè)面)荣回。
超媒體API概念的運(yùn)作跟人們的行為類似。通過(guò)請(qǐng)求API的根來(lái)獲得一個(gè)URL的列表叠赦,這個(gè)列表里面的每一個(gè)URL都指向一個(gè)集合驹马,并且提供了客戶端可以理解的信息來(lái)描述每一個(gè)集合。是否為每一個(gè)資源提供ID并不重要(或者不是必須的)除秀,只要提供URL即可糯累。
一個(gè)超媒體API一旦具有了客戶端,那么它就可以爬行鏈接并收集信息册踩,而URL總是在響應(yīng)中被更新泳姐,并且不需要如契約的一部分那樣事先被知曉。如果一個(gè)URL曾經(jīng)被緩存過(guò)暂吉,并且在隨后的請(qǐng)求中返回404錯(cuò)誤胖秒,那么客戶端可以很簡(jiǎn)單的回退到根URL并重新發(fā)現(xiàn)內(nèi)容。
在獲取集合中的一個(gè)資源列表時(shí)會(huì)返回一個(gè)屬性慕的,這個(gè)屬性包含了各個(gè)資源的完整URL阎肝。當(dāng)實(shí)施一個(gè)POST/PATCH/PUT請(qǐng)求后,響應(yīng)可以被一個(gè)3xx的狀態(tài)碼重定向到完整的資源上肮街。
JSON不僅告訴了我們需要定義哪些屬性作為URL风题,也告訴了我們?nèi)绾螌RL與當(dāng)前文檔關(guān)聯(lián)的語(yǔ)義。正如你猜的那樣,HTML就提供了這樣的信息沛硅。我們可能很樂(lè)意看到我們的API走完了完整的周期眼刃,并回到了處理HTML上來(lái)。想一下我們與CSS一起前行了多遠(yuǎn)摇肌,有一天我們可能再次看到它變成了一個(gè)通用實(shí)踐讓API和網(wǎng)站可以去使用相同的URL和內(nèi)容擂红。
文檔
老實(shí)說(shuō),即使你不能百分之百的遵循指南中的條款围小,你的API也不是那么糟糕昵骤。但是,如果你不為API準(zhǔn)備文檔的話吩抓,沒(méi)有人會(huì)知道怎么使用它涉茧,那它真的會(huì)成為一個(gè)糟糕的API。
讓你的文檔對(duì)那些未經(jīng)認(rèn)證的開發(fā)者也可用
不要使用文檔自動(dòng)化生成器疹娶,即便你用了伴栓,你也要保證自己審閱過(guò)并讓它具有更好的版式。
不要截?cái)嗍纠姓?qǐng)求與響應(yīng)的內(nèi)容雨饺,要展示完整的東西钳垮。并在文檔中使用高亮語(yǔ)法。
文檔化每一個(gè)端點(diǎn)所預(yù)期的響應(yīng)代碼和可能的錯(cuò)誤消息额港,和在什么情況下會(huì)產(chǎn)生這些的錯(cuò)誤消息
如果你有富余的時(shí)間饺窿,那就創(chuàng)建一個(gè)控制臺(tái)來(lái)讓開發(fā)者可以立即體驗(yàn)一下API的功能。創(chuàng)建一個(gè)控制臺(tái)并沒(méi)有想象中那么難移斩,并且開發(fā)者們(內(nèi)部或者第三方)也會(huì)因此而擁戴你肚医。
另外確保你的文檔能夠被打印。CSS是個(gè)強(qiáng)大的工具可以幫助到你向瓷。而且在打印的時(shí)候也不用太擔(dān)心邊側(cè)欄的問(wèn)題肠套。即便沒(méi)有人會(huì)打印到紙上,你也會(huì)驚奇的發(fā)現(xiàn)很多開發(fā)者愿意轉(zhuǎn)化成PDF格式進(jìn)行離線閱讀猖任。
勘誤:原始的HTTP封包
因?yàn)槲覀兯龅亩际腔贖TTP協(xié)議你稚,所以我將展示給你一個(gè)解析了的HTTP封包。我經(jīng)常很驚訝的發(fā)現(xiàn)有多少人不知道這些東西朱躺。當(dāng)客戶端發(fā)送一個(gè)請(qǐng)求道服務(wù)器時(shí)刁赖,他們會(huì)提供一個(gè)鍵值對(duì)集,先是一個(gè)頭长搀,緊跟著是兩個(gè)回車換行符宇弛,然后才是請(qǐng)求體。所有這些都是在一個(gè)封包里被發(fā)送源请。
服務(wù)器響應(yīng)也是同樣的鍵值對(duì)集枪芒,帶兩個(gè)回車換行符轿钠,然后是響應(yīng)體。HTTP就是一個(gè)請(qǐng)求/響應(yīng)協(xié)議病苗;它不支持“推送”模式(服務(wù)器直接發(fā)送數(shù)據(jù)給客戶端),除非你采用其他協(xié)議症汹,如Websockets硫朦。
當(dāng)你設(shè)計(jì)API時(shí),你應(yīng)該能夠使用工具去查看原始的HTTP封包背镇。Wireshark是個(gè)不錯(cuò)的選擇咬展。同時(shí),你也該采用一個(gè)框架/web服務(wù)器瞒斩,使你能夠在必要時(shí)修改某些字段的值破婆。
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
}