如果你是一個客戶端售睹、前端開發(fā)者,你可能會在某個時間吐槽過后端工程師的API設(shè)計仗嗦,原因可能是文檔不完善膘滨、返回數(shù)據(jù)丟字段、錯誤碼不清晰等稀拐。如果你是一個后端API開發(fā)者火邓,你一定在某些時候感到困惑,怎么讓接口URL設(shè)計的合理德撬,數(shù)據(jù)格式怎么定铲咨,錯誤碼怎么處理,然后怎么才能合適的描述我的API蜓洪,API怎么認證用戶的請求纤勒。
在前后端分離和微服務(wù)成為現(xiàn)代軟件開發(fā)的大趨勢下,API設(shè)計也應(yīng)該變得越來越規(guī)范和高效隆檀。本篇希望把API相關(guān)的概念最樸素的方式梳理摇天,對API設(shè)計有一個更全面和細致的認識,構(gòu)建出更規(guī)范恐仑、設(shè)計清晰和文檔完善的API泉坐。
重新認識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只是一種傳輸或媒體格式咒钟,便于計算機解析和讀取數(shù)據(jù),因此都有一個共同特點就是具有幾個基本數(shù)據(jù)類型若未,同時提供了嵌套和列表的數(shù)據(jù)表達方式朱嘴。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)準啡彬。網(wǎng)絡(luò)上有大量對RESTful風(fēng)格的解讀羹与,簡單來說Restful定義URI和HTTP狀態(tài)碼,讓你的API設(shè)計變得更簡潔庶灿、清晰和富有層次,對緩存等實現(xiàn)更有幫助吃衅。RESTful不是靈丹妙藥往踢,也不是銀彈。
RESTful第一次被提出是在2000Roy Fielding的博士論文中徘层,他也是HTTP協(xié)議標(biāo)準制定者之一峻呕。從本質(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)出任何有價值的信息间雀,為緩存機制帶來麻煩。對前端來說镊屎,在組裝請求的時候顯得麻煩不說雷蹂,另外返回到數(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ù)驳规,在以后例如負載均衡和緩存的路由非常有好處。
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只是表達被操作的資源位置,因此不應(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)格下怎么做用戶身份認證我們會在后面講到愁憔。
- 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ù)格式乃秀,后來被廣泛認可)肛著。
JSON已經(jīng)是最主流的網(wǎng)絡(luò)傳輸格式,因此本文默認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機構(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é)議來進行增刪改查柠新,那我們怎么在沒有文檔的情況下找到這些資源的地址呢窍荧,一種可行的辦法就是在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思想。下面給出一個典型的例子進一步說明:
如果在某個系統(tǒng)中產(chǎn)品和訂單是一對多的關(guān)系程梦,那我們給產(chǎn)品的返回值可以定義為:
從返回中我們能得到links中product的的資源地址点把,同時也能得到orders的地址,這樣我們不需要客戶端自己拼裝地址屿附,就能夠得到請求orders的地址郎逃。如果我們嚴格按照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:中文版更新不及時伦吠,請以英文文檔為準)
文/ThoughtWorks 林寧