細(xì)說API - 重新認(rèn)識RESTful

如果你是一個客戶端、前端開發(fā)者,你可能會在某個時間吐槽過后端工程師的API設(shè)計,原因可能是文檔不完善、返回數(shù)據(jù)丟字段、錯誤碼不清晰等。如果你是一個后端API開發(fā)者,你一定在某些時候感到困惑,怎么讓接口URL設(shè)計的合理,數(shù)據(jù)格式怎么定,錯誤碼怎么處理,然后怎么才能合適的描述我的API,API怎么認(rèn)證用戶的請求。

在前后端分離和微服務(wù)成為現(xiàn)代軟件開發(fā)的大趨勢下,API設(shè)計也應(yīng)該變得越來越規(guī)范和高效。本篇希望把API相關(guān)的概念最樸素的方式梳理,對API設(shè)計有一個更全面和細(xì)致的認(rèn)識,構(gòu)建出更規(guī)范、設(shè)計清晰和文檔完善的API。

重新認(rèn)識API

廣義的API(Application Programming Interface)是指應(yīng)用程序編程接口,包括在操作系統(tǒng)中的動態(tài)鏈接庫文件例如dll\so,或者基于TCP層的socket連接,用來提供預(yù)定義的方法和函數(shù),調(diào)用者無需訪問源碼和理解內(nèi)部原理便可實現(xiàn)相應(yīng)功能。而當(dāng)前通常指通過HTTP協(xié)議傳輸?shù)膚eb service技術(shù)。

API在概念上和語言無關(guān),理論上具有網(wǎng)絡(luò)操作能力的所有編程語言都可以提供API服務(wù)。Java、PHP、Node甚至C都可以實現(xiàn)web API,都是通過響應(yīng)HTTP請求并構(gòu)造HTTP包來完成的,但是內(nèi)部實現(xiàn)原理不同。例如QQ郵箱就是通過使用了C構(gòu)建CGI服務(wù)器實現(xiàn)的。

API在概念上和JSON和XML等媒體類型無關(guān),JSON和XML只是一種傳輸或媒體格式,便于計算機(jī)解析和讀取數(shù)據(jù),因此都有一個共同特點就是具有幾個基本數(shù)據(jù)類型,同時提供了嵌套和列表的數(shù)據(jù)表達(dá)方式。JSON因為更加輕量、容易解析、和JavaScript天生集成,因此成為現(xiàn)在主流傳輸格式。在特殊的場景下可以構(gòu)造自己的傳輸格式,例如JSONP傳輸?shù)膶嶋H上是一段JavaScript代碼來實現(xiàn)跨域。

基于以上,API設(shè)計的目的是為了讓程序可讀,應(yīng)當(dāng)遵從簡單、易用、無狀態(tài)等特性,這也是為什么Restful風(fēng)格流行的原因。

RESTful

REST(英文:Representational State Transfer,簡稱REST),RESTful是一種對基于HTTP的應(yīng)用設(shè)計風(fēng)格,只是提供了一組設(shè)計原則和約束條件,而不是一種標(biāo)準(zhǔn)。網(wǎng)絡(luò)上有大量對RESTful風(fēng)格的解讀,簡單來說Restful定義URI和HTTP狀態(tài)碼,讓你的API設(shè)計變得更簡潔、清晰和富有層次,對緩存等實現(xiàn)更有幫助。RESTful不是靈丹妙藥,也不是銀彈。

RESTful第一次被提出是在2000Roy Fielding的博士論文中,他也是HTTP協(xié)議標(biāo)準(zhǔn)制定者之一。從本質(zhì)上理解RESTful,它其實是盡可能復(fù)用HTTP特性來規(guī)范軟件設(shè)計,甚至提高傳輸效率。HTTP包處于網(wǎng)絡(luò)應(yīng)用層,因此HTTP包為平臺無關(guān)的字符串表示,如果盡可能的使用HTTP的包特征而不是大量在body定義自己的規(guī)則,可以用更簡潔、清晰、高效的方式實現(xiàn)同樣的需求。

用我?guī)啄昵耙粋€真實的例子,我們?yōu)榱颂峁┮粋€訂單信息API,為了更方便傳遞信息全部使用了POST請求,使用了定義了method表明調(diào)用方法:

返回定義了自己的狀態(tài):

大家現(xiàn)在來看例子會覺得設(shè)計上很糟糕,但是在當(dāng)時大量的API是這樣設(shè)計的。操作資源的動作全部在數(shù)據(jù)體里面重新定義了一遍,URL上不能體現(xiàn)出任何有價值的信息,為緩存機(jī)制帶來麻煩。對前端來說,在組裝請求的時候顯得麻煩不說,另外返回到數(shù)據(jù)的時候需要檢查HTTP的狀態(tài)是不是200,還需要檢查status字段。

那么使用RESTful的例子是什么樣呢:

例子中使用路徑參數(shù)構(gòu)建URL和HTTP動詞來區(qū)分我們需要對服務(wù)所做出的操作,而不是使用URL上的接口名稱,例如 getProducts等;使用HTTP狀態(tài)碼,而不是在body中自定義一個狀態(tài)碼字段;URL需要有層次的設(shè)計,例如/catetory/{category_id}/products 便于獲取path參數(shù),在以后例如負(fù)載均衡和緩存的路由非常有好處。

RESTful的本質(zhì)是基于HTTP協(xié)議對資源的增刪改查操作做出定義。理解HTTP協(xié)議非常簡單,HTTP是通過網(wǎng)絡(luò)socket發(fā)送一段字符串,這個字符串由鍵值對組成的header部分和純文本的body部分組成。Url、Cookie、Method都在header中。

幾個典型的RESTful API場景:

雖然HTTP協(xié)議定義了其他的Method,但是就普通場景來說,用好上面的幾項已經(jīng)足夠了

RESTful的幾個注意點:

  • URL只是表達(dá)被操作的資源位置,因此不應(yīng)該使用動詞,且注意單復(fù)數(shù)區(qū)分
  • 除了POST和DELETE之外,其他的操作需要冥等的,例如對數(shù)據(jù)多次更新應(yīng)該返回同樣的內(nèi)容
  • 設(shè)計風(fēng)格沒有對錯之分,RESTful一種設(shè)計風(fēng)格,與此對應(yīng)的還有RPC甚至自定義的風(fēng)格
  • RESTful和語言、傳輸格式無關(guān)
  • 無狀態(tài),HTTP設(shè)計本來就是沒有狀態(tài)的,之所以看起來有狀態(tài)因為我們?yōu)g覽器使用了Cookies,每次請求都會把Session ID(可以看做身份標(biāo)識)傳遞到headers中。關(guān)于RESTful風(fēng)格下怎么做用戶身份認(rèn)證我們會在后面講到。
  • RESTful沒有定義body中內(nèi)容傳輸?shù)母袷?,有另外的?guī)范來描述怎么設(shè)計body的數(shù)據(jù)結(jié)構(gòu),網(wǎng)絡(luò)上有些文章對RESTful的范圍理解有差異

JSON API

因為RESTful風(fēng)格僅僅規(guī)定了URL和HTTP Method的使用,并沒有定義body中數(shù)據(jù)格式的。我們怎么定義請求或者返回對象的結(jié)構(gòu),以及該如何針對不同的情況返回不同的HTTP 狀態(tài)碼?

同樣的,這個世界上已經(jīng)有人注意到這個問題,有一份叫做JSON API開源規(guī)范文檔描述了如何傳遞數(shù)據(jù)的格式,JSON API最早來源于Ember Data(Ember是一個JavaScript前端框架,在框架中定義了一個通用的數(shù)據(jù)格式,后來被廣泛認(rèn)可)。

JSON已經(jīng)是最主流的網(wǎng)絡(luò)傳輸格式,因此本文默認(rèn)JSON作為傳輸格式來討論后面的話題。JSONAPI嘗試去提供一個非常通用的描述數(shù)據(jù)資源的格式,關(guān)于記錄的創(chuàng)建、更新和刪除,因此要求在前后端均容易實現(xiàn),并包含了基本的關(guān)系類型。個人理解,它的設(shè)計非常接近數(shù)據(jù)庫ORM輸出的數(shù)據(jù)類型,和一些Nosql(例如MongoDB)的數(shù)據(jù)結(jié)構(gòu)也很像,從而對前端開發(fā)者來說擁有操作數(shù)據(jù)庫或數(shù)據(jù)集合的體驗。另外一個使用這個規(guī)范的好處是,已經(jīng)有大量的庫和框架做了相關(guān)實現(xiàn),例如,backbone-jsonapi ,json-patch。

沒有必要把JSON API文檔全部搬過來,這里重點介紹常用部分內(nèi)容。

MIME 類型

JSON API數(shù)據(jù)格式已經(jīng)被IANA機(jī)構(gòu)接受了注冊,因此必須使用application/vnd.api+json類型??蛻舳苏埱箢^中Content-Type應(yīng)該為application/vnd.api+json,并且在Accept中也必須包含application/vnd.api+json。如果指定錯誤服務(wù)器應(yīng)該返回415或406狀態(tài)碼。

JSON文檔結(jié)構(gòu)

在頂級節(jié)點使用data、errors、meta,來描述數(shù)據(jù)、錯誤信息、元信息,注意data和errors應(yīng)該互斥,不能再一個文檔中同時存在,meta在項目實際上用的很少,只有特別情況才需要用到,比如返回服務(wù)器的一些信息。

data屬性

一個典型的data的對象格式,我們的有效信息一般都放在attributes中。

  • id顯而易見為唯一標(biāo)識,可以為數(shù)字也可以為hash字符串,取決于后端實現(xiàn)
  • type 描述數(shù)據(jù)的類型,可以對應(yīng)為數(shù)據(jù)模型的類名
  • attributes 代表資源的具體數(shù)據(jù)
  • relationships、links為可選屬性,用來放置關(guān)聯(lián)數(shù)據(jù)和資源地址等數(shù)據(jù)

errors屬性

這里的errors和data有一點不同,一般來說返回值中errors作為列表存在,因為針對每個資源可能出現(xiàn)多個錯誤信息。最典型的例子為,我們請求的對象中某些字段不符合驗證要求,這里需要返回驗證信息,但是HTTP狀態(tài)碼會使用一個通用的401,然后把具體的驗證信息在errors給出來。

在title字段中給出錯誤信息,如果我們在本地或者開發(fā)環(huán)境想打出更多的調(diào)試堆棧信息,我們可以增加一個detail字段讓調(diào)試更加方便。需要注意的一點是,我們應(yīng)該在生產(chǎn)環(huán)境屏蔽部分敏感信息,detail字段最好在生產(chǎn)環(huán)境不可見。

常用的返回碼

返回碼這部分是我開始設(shè)計API最感到迷惑的地方,如果你去查看HTTP協(xié)議文檔,文檔上有幾十個狀態(tài)碼讓你無從下手。實際上我們能在真實環(huán)境中用到的并不多,這里會介紹幾個典型的場景。

200 OK

200是一個最常用的狀態(tài)碼用來表示請求成功,例如GET請求到某一個資源,或者更新、刪除某資源。 需要注意的是使用POST創(chuàng)建資源應(yīng)該返回201表示數(shù)據(jù)被創(chuàng)建。

201 Created

如果客戶端發(fā)起一個POST請求,在RESTful部分我們提到,POST為創(chuàng)建資源,如果服務(wù)器處理成功應(yīng)該返回一個創(chuàng)建成功的標(biāo)志,在HTTP協(xié)議中,201為新建成功的狀態(tài)。文檔規(guī)定,服務(wù)器必須在data中返回id和type。 下面是一個HTTP的返回例子:

在HTTP協(xié)議中,2XX的狀態(tài)碼都表示成功,還有202、204等用的較少,就不做過多介紹了,4XX返回客戶端錯誤,會重點介紹。

401 Unauthorized

如果服務(wù)器在檢查用戶輸入的時候,需要傳入的參數(shù)不能滿足條件,服務(wù)器可以給出401錯誤,標(biāo)記客戶端錯誤,需要客戶端自查。

415 Unsupported Media Type

當(dāng)服務(wù)器媒體類型Content-Type和Accept指定錯誤的時候,應(yīng)該返回415。

403 Forbidden

當(dāng)客戶端訪問未授權(quán)的資源時,服務(wù)器應(yīng)該返回403要求用戶授權(quán)信息。

404 Not Found

這個太常見了,當(dāng)指定資源找不到時服務(wù)器應(yīng)當(dāng)返回404。

500 Internal Server Error

當(dāng)服務(wù)器發(fā)生任何內(nèi)部錯誤時,應(yīng)當(dāng)返回500,并給出errors字段,必要的時候需要返回錯誤的code,便于查錯。一般來說,500錯誤是為了區(qū)分4XX錯誤,包括任何服務(wù)器內(nèi)部技術(shù)或者業(yè)務(wù)異常都應(yīng)該返回500。

HATEOAS

這個時候有些了解過HATEOAS同學(xué)會覺得上面的links和HATEOAS思想很像,那么HATEOAS是個什么呢,為什么又有一個陌生的名詞要學(xué)。 實際上HATEOAS算作被JSON API定義了的一部分,HATEOAS思想是既然Restful是利用HTTP協(xié)議來進(jìn)行增刪改查,那我們怎么在沒有文檔的情況下找到這些資源的地址呢,一種可行的辦法就是在API的返回體里面加入導(dǎo)航信息,也就是links。這樣就像HTML中的A標(biāo)簽實現(xiàn)了超文本文檔一樣,實現(xiàn)了超鏈接JSON文檔。

超鏈接JSON文檔是我造的一個詞,它的真是名字是Hypermedia As The Engine Of Application State,中文叫做超媒體應(yīng)用程序狀態(tài)的引擎,網(wǎng)上很多講它。但是它并不是一個很高大上的概念,在RESTful和JSONAPI部分我們都貫穿了HATEOAS思想。下面給出一個典型的例子進(jìn)一步說明:

如果在某個系統(tǒng)中產(chǎn)品和訂單是一對多的關(guān)系,那我們給產(chǎn)品的返回值可以定義為:

從返回中我們能得到links中product的的資源地址,同時也能得到orders的地址,這樣我們不需要客戶端自己拼裝地址,就能夠得到請求orders的地址。如果我們嚴(yán)格按照HATEOAS開發(fā),客戶端只需要在配置文件中定義一個入口地址就能夠完成所有操作,在資源地址發(fā)生變化的時候也能自動適配。

當(dāng)然,在實際項目中要使用HATEOAS也要付出額外的工作量(包括開發(fā)和前后端聯(lián)調(diào)),HATEOAS只是一種思想,怎么在項目中使用也需要靈活應(yīng)對了。

參考鏈接

在文檔中還定義了分頁、過濾、包含等更多內(nèi)容,請移步文檔:

英文版:http://jsonapi.org/format/

中文版:http://jsonapi.org.cn/format/ (PS:中文版更新不及時,請以英文文檔為準(zhǔn))


文/ThoughtWorks 林寧

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容