如今微服務(wù)和分布式架構(gòu)變的越來越流行,而簡單,可靠,高效,跨平臺和跨語言的 Web Service 則是這類系統(tǒng)架構(gòu)的基石。 RESTful Web Service 恰好滿足這些特點,被越來越多的系統(tǒng)架構(gòu)所采用。
本文主要面向?qū)?Web Service 有一定理解,需要進(jìn)一步了解基于 REST 形式的 Web Service 的 IT 開發(fā)人員和架構(gòu)師。它不是 Web Service 入門介紹,你需要較多相關(guān)領(lǐng)域的知識背景才能理解全部內(nèi)容。
作為互聯(lián)網(wǎng)應(yīng)用開發(fā)人員,我們經(jīng)常能看到 Web Service,REST 和 RESTful Web Service 之類的描述,可我們真的清楚這些概念嗎?
Web Service 簡單來說是指提供給不同設(shè)備通過互聯(lián)網(wǎng)(一般使用 HTTP 協(xié)議)進(jìn)行通信和交換數(shù)據(jù)的一種服務(wù)。RESTful Web Service 是實現(xiàn) Web Service 的一種方式。那么到底什么是 RESTful Web Service呢?什么又是 REST 呢?
REST (Representational State Transfer) 是由美國計算機(jī)科學(xué)家 Roy Fielding在2000年的博士論文提出的一種架構(gòu)方式。Roy Fielding絕對可以稱之為業(yè)界大牛,他現(xiàn)任 Adobe 首席科學(xué)家,是HTTP協(xié)議的首要作者之一,也是Apache項目的聯(lián)合創(chuàng)始人。
REST 是一種架構(gòu)方式和約定,和具體實現(xiàn)無關(guān),也不一定必須基于Web。我們一般把采用 REST 架構(gòu)的 Web Service 稱之 RESTful Web Service。在實際項目應(yīng)用中,嚴(yán)格來說,我們應(yīng)該稱這種 Web Service 為具有 REST 風(fēng)格的 Web Service。原因是我們在處理和解決某些實際問題時,這種 Service 可能并不完全嚴(yán)格遵守 REST 架構(gòu)的所有必要約定。
RESTful Web Service 和基于SOAP的 Web Services 有著本質(zhì)的不同。使用 SOAP 的 Web Service 實際上是以協(xié)議(protocol)的形式工作的。因為 SOAP Web Service 嚴(yán)格規(guī)定了如何發(fā)現(xiàn)和描述 API,其傳輸?shù)南⒁灿袊?yán)格統(tǒng)一的格式(例如傳輸?shù)妮d體XML有嚴(yán)格的格式規(guī)范)。而 RESTful Web Service 并不是協(xié)議,它沒有規(guī)定傳輸消息的具體格式,它只是一種約定使用 REST 架構(gòu)實現(xiàn)的 Web Service。RESTful Web Service 相比SOAP Web Service 更加簡單和輕量級。現(xiàn)在大部分 RESTful Web Service 都使用類似的形式,例如都使用HTTP傳輸,使用風(fēng)格類似的 URL 作為 API 和 使用 JSON 或者 XML 來傳輸數(shù)據(jù)等等(目前 JSON 占據(jù)主導(dǎo)地位,并且有持續(xù)流行的趨勢[1])。
如果你之前沒有接觸過SOAP,你只需記住RESTful Web Service 是一種采用 REST 架構(gòu)約定,更簡單,更輕量級的Web Service
REST 之父大牛 Roy Fielding 在他的論文中,一共從下面6個方面闡述了 REST 架構(gòu)的約定(REST 應(yīng)該滿足哪些條件,以及這樣做會有什么好處)
CS結(jié)構(gòu)(Client–server)
客戶端是一個相對獨立的實現(xiàn),它不必考慮數(shù)據(jù)的持久化存儲問題。服務(wù)端擁有和保存數(shù)據(jù),服務(wù)端不去關(guān)心客戶端內(nèi)部實現(xiàn),也不用關(guān)心客戶端請求的上下文。服務(wù)端和客戶端之間遵守相同的接口規(guī)范。在遵守相同接口規(guī)范的前提下,二者都可以獨立演化,甚至可以被其它的實現(xiàn)替代。
無狀態(tài)(Stateless)
任何時候一個客戶端的請求數(shù)據(jù)都包含能夠讓服務(wù)端完成請求的充分信息,服務(wù)端不依賴前后不同請求的順序和狀態(tài)信息來完成請求。請求的session信息由客戶端持有,并在必要時連同請求數(shù)據(jù)一起發(fā)送。服務(wù)端可以使用請求中的session信息去其它外部服務(wù)或者數(shù)據(jù)庫獲取相關(guān)內(nèi)容進(jìn)以便對該請求做權(quán)限驗證等操作。
如果所需數(shù)據(jù)要通過多次請求才能完成,客戶端必須自己負(fù)責(zé)記錄狀態(tài)(因為服務(wù)器不跟蹤客戶端的狀態(tài)),在下次請求時附帶發(fā)出。一個典型的例子是分頁的實現(xiàn),客戶端需要自己保存當(dāng)前頁數(shù),請求下一頁時作為參數(shù)一起發(fā)給服務(wù)端,服務(wù)端使用該參數(shù)返回正確的下一頁數(shù)據(jù)。有些設(shè)計,比如Facebook API,服務(wù)端返回數(shù)據(jù)中包含下一頁數(shù)據(jù)的請求URL,客戶端只要記錄這個URL即可發(fā)起下一頁的請求。
服務(wù)端不依賴客戶端的請求順序和狀態(tài)提高了服務(wù)器的可擴(kuò)展性(scalability)。比如在使用Load balancer的情況下,不能因為某個請求被分配到其它服務(wù)器而丟失某些信息從而返回不正確的數(shù)據(jù)。
無狀態(tài)的約定也提高了系統(tǒng)的健壯性(reliability)。如果集群服務(wù)器中的其中一臺發(fā)生故障也不會對系統(tǒng)的平穩(wěn)運(yùn)行造成太大影響。
不過無狀態(tài)的約定也是有缺點的,客戶端必須每次都要帶上相同重復(fù)的信息來確定自己的身份和狀態(tài),這就造成了傳輸數(shù)據(jù)的冗余性。然而沒有一個架構(gòu)決策是十全十美的,最終的決策往往都是相互妥協(xié)的結(jié)果,我們只能選擇一個相對有優(yōu)勢的決策。
緩存機(jī)制(Cacheable)
服務(wù)端應(yīng)該明確規(guī)定返回數(shù)據(jù)的緩存機(jī)制,包括是否可緩存,緩存如何失效以及利用緩存獲取增量數(shù)據(jù)而不必每次獲取全部數(shù)據(jù)等。合理的緩存設(shè)計可以減少請求次數(shù),進(jìn)而提高服務(wù)器的效率和性能。
系統(tǒng)分層(Layered system)
客戶端不用知道數(shù)據(jù)是從服務(wù)端直接返回還是通過中轉(zhuǎn)代理返回。這樣的設(shè)計也同樣提高了系統(tǒng)的可擴(kuò)展性。比如可以使用負(fù)責(zé)均衡和反向代理等技術(shù)來對系統(tǒng)進(jìn)行水平擴(kuò)展和緩存處理,把系統(tǒng)劃分成不同的層次。使用分層設(shè)計也方便我們管理不同資源的權(quán)限,有利于提高系統(tǒng)的安全性。
可定制代碼(可選)(Code on demand)
服務(wù)端可選擇臨時給客戶端下發(fā)一些功能代碼讓客戶端來執(zhí)行,從而定制和擴(kuò)展客戶端的某些功能。比如服務(wù)端可以返回一些 Javascript 代碼讓客戶端執(zhí)行,去實現(xiàn)某些特定的功能。
一致的接口(Uniform interface)
一致的接口對 REST 服務(wù)至關(guān)重要?;诮y(tǒng)一的接口規(guī)則可以簡化系統(tǒng)實現(xiàn),降低子系統(tǒng)之間的耦合度,因為子系統(tǒng)只要關(guān)注實現(xiàn)接口即可。在保證接口一致的情況下,不同的實現(xiàn)可以各自獨立演化。對于接口的要求有這么四個方面:
一致的數(shù)據(jù)格式
雖然服務(wù)端內(nèi)部不同數(shù)據(jù)的存儲格式可能千差萬別,但返回給客戶端的數(shù)據(jù)一定要有一個統(tǒng)一的表現(xiàn)形式。比如 Web Service 請求返回格式要么是 HTML,要么是 XML,要么是 JSON,不能返回服務(wù)端自己內(nèi)部使用的特殊格式。
可以對已有數(shù)據(jù)進(jìn)一步操作(Resource Identifiers)
如果客戶端擁有一個資源,必要時,客戶端應(yīng)該擁有足有的信息去修改和刪除這個資源。通常我們只要在返回的數(shù)據(jù)中包含一個 UID 即可做到這點。比如從服務(wù)端獲得了一個訂單數(shù)據(jù),這個訂單數(shù)據(jù)里應(yīng)該保證有一個唯一的訂單 ID,當(dāng)我們想對這個訂單進(jìn)行進(jìn)一步操作時,可以保障操作的是同一個訂單。
數(shù)據(jù)具有自我描述性
每項數(shù)據(jù)應(yīng)該是可以自我描述的,方便代碼去處理和解析其中的內(nèi)容。比如通過HTTP返回的數(shù)據(jù)里面有 [MIME type ]信息,我們從MIME type里面可以知道數(shù)據(jù)的具體格式,是圖片,視頻還是JSON。
REST 的這些約定為我們設(shè)計 RESTful Web Service 提供了一個絕佳的范本。套用這個范本,在設(shè)計 Web Service 時,可以在一定程度上避免很多問題和少走彎路。因為這些約定本質(zhì)上都是對現(xiàn)實系統(tǒng)設(shè)計優(yōu)點的一些提煉和總結(jié),它已經(jīng)比較周全的為我們考慮了可能出現(xiàn)的問題,并且提供了解決問題的基本原則和方向。
本文第三小節(jié)“REST 架構(gòu)約定”的主要內(nèi)容來自對參考文檔中的部分內(nèi)容的整理和翻譯。計算機(jī)科學(xué)有很多非常有價值的論文,很多論文是如此的重要以至于能奠定一個時代的技術(shù)基礎(chǔ)(比如 Google 的 MapReduce,可能需自備梯子)。我們只有充分理解了其中的思想和原則才能作出更好的設(shè)計和架構(gòu)。
下一篇文章我們將結(jié)合HTTP的特性,探討如何設(shè)計 RESTful Web Service API
[1]:這個結(jié)論來自Goolge Trends(須自備梯子): https://www.google.com/trends/explore?date=all&q=xml%20api,json%20api