越來越多人將任何基於 HTTP 的介面稱為 REST API,這讓我感到沮喪。今天的例子是 SocialSite REST API。那是 RPC。它大聲喊著 RPC。它所展示的耦合性如此之高,應該被評為 X 級。
要如何讓 REST 架構風格清楚地知道超文字是一種限制?換句話說,如果應用程式狀態的引擎(因此也就是 API)不是由超文字驅動的,那麼它就不可能是 RESTful,也不可能是 REST API。句點。是不是有什麼破損的手冊需要修復?
–Roy Fielding,REST 一詞的創作者
REST 肯定是電腦程式設計歷史上被最廣泛誤用的技術術語。
我想不出有什麼其他東西可以相提並論。
今天,當有人使用 REST 這個詞時,他們幾乎總是在討論基於 JSON 使用 HTTP 的 API。
當你看到徵才啟事提到 REST 或公司討論 REST 指南時,他們很少提到超文字或超媒體:他們反而會提到 JSON、GraphQL(!)之類的。
只有少數固執的人會抱怨:但這些 JSON API 並非 RESTful!
在這篇文章中,我想給你一個關於 REST 的 簡短、不完整且大多錯誤的歷史,以及我們如何走到這個地步,讓它的意思幾乎完全反轉,變成與 REST 最初對比的:RPC。
REST 一詞,REpresentational State Transfer(具象狀態傳輸)的縮寫,來自於 Fielding 博士論文的第五章。Fielding 描述了(當時全新的)全球資訊網的網路架構,並將其與其他可能的網路架構,特別是 RPC 風格的網路架構進行比較。
重要的是要理解,在他撰寫時(1999-2000),沒有 JSON API:他描述的是當時存在的網路,人們在「瀏覽網頁」時,透過 HTTP 交換 HTML。JSON 還沒有被創造出來,廣泛採用 JSON 還需要十年時間。
REST 描述的是一種網路架構,並且是根據 API 的約束來定義的,這些約束需要滿足才能被視為 RESTful API。這些語言是學術性的,這導致了對該主題的混淆,但它已經足夠清晰,大多數開發人員應該能夠理解它。
REST 內部有許多約束和概念,但我認為有一個關鍵的想法是 REST 的定義性特徵,也是它與其他可能網路架構最顯著的區別。
這被稱為 統一介面約束,更具體地說,在這個概念中,超媒體作為應用程式狀態引擎(HATEOAS) 的概念,或者如 Fielding 喜歡稱呼它的,超媒體約束。
為了理解這個統一介面約束,讓我們考慮兩個 HTTP 回應,它們返回有關銀行帳戶的資訊,第一個是 HTML(超文字),第二個是 JSON。
HTTP/1.1 200 OK
<html>
<body>
<div>Account number: 12345</div>
<div>Balance: $100.00 USD</div>
<div>Links:
<a href="/accounts/12345/deposits">deposits</a>
<a href="/accounts/12345/withdrawals">withdrawals</a>
<a href="/accounts/12345/transfers">transfers</a>
<a href="/accounts/12345/close-requests">close-requests</a>
</div>
<body>
</html>
HTTP/1.1 200 OK
{
"account_number": 12345,
"balance": {
"currency": "usd",
"value": 100.00
},
"status": "good"
}
這兩個回應之間的關鍵差異,以及為什麼HTML 回應是 RESTful,而JSON 回應不是的原因是
HTML 回應是完全自我描述的。
接收此回應的正確超媒體客戶端不知道什麼是銀行帳戶,什麼是餘額等等。它只知道如何呈現超媒體,HTML。
客戶端對與此資料相關聯的 API 端點一無所知,除了透過 HTML 本身中可發現的 URL 和超媒體控制項(連結和表單)。如果資源的狀態發生變化,使得該資源上可用的允許動作發生變化(例如,如果帳戶透支),則 HTML 回應將會發生變化以顯示可用的新動作集。
客戶端將呈現這個新的 HTML,完全不知道「透支」是什麼意思,甚至不知道什麼是銀行帳戶。
超文字就是以這種方式成為應用程式狀態的引擎:HTML 回應「攜帶」了所有直接與系統互動所需的 API 資訊。
現在,將其與第二個 JSON 回應進行比較。
在這種情況下,訊息並非自我描述的。相反,客戶端必須知道如何解釋 status
欄位以顯示適當的使用者介面。此外,客戶端必須根據「頻外」資訊,也就是來自回應之外的另一個資訊來源(例如 swagger API 文件)的 URL、參數等資訊,知道帳戶上可用的動作。
JSON 回應不是自我描述的,並且沒有在超媒體中編碼資源的狀態。因此,它無法滿足 REST 的統一介面約束,因此不是 RESTful。
在 REST API 必須由超文字驅動中,Fielding 接著說
REST API 的進入點應該是除了初始 URI(書籤)和一組適用於目標受眾的標準化媒體類型(也就是說,任何可能使用 API 的客戶端都應該理解)之外,沒有任何事先的知識。從那一點開始,所有應用程式狀態轉換都必須由客戶端選擇伺服器提供的選項來驅動,這些選項存在於接收到的表示中,或由使用者對這些表示的操作所暗示。
因此,在 RESTful 系統中,您應該能夠透過單個 URL 進入系統,並且從那一點開始,系統內的所有導航和動作都應該完全透過自我描述的超媒體提供:例如,透過 HTML 中的連結和表單。在進入點之外,在正確的 RESTful 系統中,API 客戶端不需要關於您的 API 的任何額外資訊。
這就是 RESTful 系統令人難以置信的靈活性的來源:由於所有回應都是自我描述的,並且編碼了所有當前可用的動作,因此無需擔心例如對 API 進行版本控制!事實上,您甚至不需要記錄它!
如果事情發生變化,超媒體回應也會發生變化,就是這樣。
這是一個構建分散式系統的極其靈活和創新的概念。
今天,大多數網頁開發人員和大多數公司都會將第二個範例稱為 RESTful API。
他們甚至可能不會將第一個回應視為 API 回應。它只是 HTML!
(可憐的 HTML,不受尊重。)
API 總是 JSON,或者如果你喜歡的話,可以是像 Protobuf 之類的東西,對吧?
錯了。
你們都錯了,應該感到羞愧。
第一個回應是 API 回應,而且,事實上,它是 RESTful 的回應!
第二個回應實際上是一種遠端程序呼叫(RPC)風格的 API。客戶端和伺服器是耦合的,就像 Fielding 在 2008 年抱怨的 SocialSite API 一樣:客戶端需要有關於它正在處理的資源的額外知識,這些知識必須來自 JSON 回應本身之外的其他來源。
這個 API 在精神上幾乎與 REST 相反。
讓我們將這種風格的 API 稱為「不安的」(RESTless)。
現在,我們究竟是如何走到這樣一個地步,讓 99.9% 的業界將明顯不是 RESTful 的 API 稱為 RESTful 的?
這是一個有趣的故事
Roy Fielding 在 2000 年發表了他的論文。
大約在同一時間,XML-RPC,一種明確受 RPC 啟發的協定被發布,並開始作為一種使用 HTTP 構建 API 的方法而聚集力量。XML-RPC 是微軟的一個名為 SOAP 的較大專案的一部分。XML-RPC 源自 RPC 風格協定的悠久傳統,主要來自企業界,其中也加入了大量的靜態類型和早期的 XML 最大主義。
在這個時候出現的還有 AJAX,或非同步 JavaScript 和 XML。請注意此處的 XML。現在大家都知道,AJAX 允許瀏覽器在後台向伺服器發出 HTTP 請求,並直接在 JavaScript 中處理回應,為網頁開闢了一個全新的程式設計世界。
問題是:這些請求應該是什麼樣的?它們顯然會是 XML。看,它就在名稱中。而這個新的 SOAP/XML-RPC 標準已經發布,也許那是正確的選擇?
有些人注意到網路具有 Fielding 所描述的這種不同的架構,並開始詢問 REST 而不是 SOAP 是否應該是存取所謂「網路服務」的首選機制。網路被證明是非常靈活的,並且增長迅猛,所以也許對於瀏覽器和人類來說效果很好的相同網路架構 REST 對於 API 來說也會效果很好。
這聽起來很合理,特別是當 XML 是 API 的格式時:XML 看起來確實很像 HTML,不是嗎?您可以想像一個 XML API 滿足所有 RESTful 約束,包括統一介面。
所以人們也開始探索這條路。
當這一切發生時,另一項重要的技術正在誕生:JSON
JSON(字面上)是 JavaScript 對 SOAP/RPC-XML 的 Java:簡單、動態且容易。現在很難相信,當 JSON 是大多數網路 API 的主要格式時,JSON 實際上花了一段時間才流行起來。早在 2008 年,關於 API 開發的討論主要圍繞 XML,而不是 JSON。
2008 年,Martin Fowler 發表了一篇文章,普及了 Richardson 成熟度模型,這是一個用於確定給定 API 的 RESTful 程度的模型。
該模型提出了四個「級別」,第一級是樸素的舊 XML,或 POX 的沼澤。
從那裡開始,當 API 採用以下概念時,可以將其視為更「成熟」的 REST API
GET
、POST
、DELETE
等)第三級是統一介面發揮作用的地方,這就是為什麼這一級被認為是最成熟且真正「REST 的榮耀」
不幸的是,對於 REST 這個術語而言,當時發生了兩件事
JSON 迅速接管了網路服務/API 的世界,因為 SOAP/XML-RPC 的過度設計實在太過分了。JSON 很簡單,「直接可以用」,而且容易閱讀和理解。
隨著這個改變,網路開發的世界徹底擺脫了 J2EE 的思維模式,將 SOAP/XML-RPC 貶為僅限企業使用的東西。
由於 REST 方法不像 SOAP/XML-RPC 那樣與 XML 緊密相連,而且它沒有對端點施加太多形式,因此 REST 自然成為 JSON 接管的地方。而且它很快就這樣做了。
在這個關鍵的轉變期間,越來越清楚的是:大多數 JSON API 都停留在 RMM 的第二級。
有些透過在其響應中加入超媒體控制而推進到第三級,但幾乎所有這些 API 仍然需要發布文件,這表明「REST 的榮耀」並未實現。
JSON 作為響應格式的接管也應該是一個強烈的暗示:JSON 明顯不是超文本。你可以在其之上施加超媒體控制,但它不是自然的。XML 至少看起來有點像 HTML,所以你有可能用它來創建超媒體。
JSON 只是...資料。添加超媒體控制很麻煩,不標準化,而且很少以統一介面約束所描述的方式使用。
儘管存在這些困難,REST 這個術語仍然存在:REST 是 SOAP 的反面,JSON API 不是 SOAP,因此 JSON API 就是 REST。
這就是我們走到今天這一步的一句話版本。
儘管 JSON API 的世界從未持續實現真正的 RESTful API,但在創建的 RESTless API 是否「RESTful」的問題上,還是有很多爭論:關於 URL 佈局的爭論、關於哪個 HTTP 動詞適合特定動作的爭論、關於媒體類型的論戰等等。
我當時還很年輕,整個事情給我的印象是模糊不清、清教徒式的和令人疏遠的,所以我幾乎放棄了整個 REST 的想法:那是些自以為是的人在網路上爭論的東西。
我很少看到被提及的(或者,當我看到時,我不理解的)是統一介面的概念,以及它對於 RESTful 系統的重要性。
直到我創建了 intercooler.js,一些聰明的人開始告訴我它是 RESTful 的,我才再次對這個想法產生興趣。
RESTful?那是 JSON API 的東西,我的前端函式庫怎麼可能 RESTful?
所以我研究了一下,用新的眼光重新閱讀了 Fielding 的論文,結果發現,不僅 intercooler 是 RESTful 的,而且我所處理的所有「RESTful」JSON API 根本不是 RESTful 的!
於是,我開始在網路上煩人地說教
最終,大多數人厭倦了嘗試在 JSON API 中添加超媒體控制,並放棄了它。雖然這些控制在某些特定的情況下(例如,分頁)運作良好,但它們從未像 REST 在一般、以人為本的網路上發現的那樣,實現廣泛且明顯的實用性。(我有一個關於原因的理論。)
事情演變成這種中間的 RESTless 狀態,REST 慢慢地鞏固了其作為 RMM 第一或第二級的 JSON API 的含義。但我們總是有可能突破到第三級和 REST 的榮耀。
然後單頁應用程式 (SPA) 就出現了。
當 SPA 出現時,網路開發完全與最初的底層 RESTful 架構脫鉤。SPA 應用程式的整個網路架構都轉向了 JSON RPC 樣式。此外,由於這些應用程式的複雜性,開發人員開始專注於前端和後端。
前端開發人員顯然沒有做任何 RESTful 的事情:他們使用 JavaScript、建立 DOM 物件,並在需要時呼叫 AJAX API。這更像是厚客戶端編寫,而不是早期網路的任何東西。
後端工程師在某種程度上仍然關注網路架構,他們繼續使用「REST」這個術語來描述他們正在做的事情。
即使他們正在做諸如為其 RESTful API 發布 swagger 文件或抱怨其 RESTful API 的 API 變動之類的事情,如果他們實際上是在建立 RESTful API,這些事情就不會發生。
最後,在 2010 年代後期,人們受夠了:即使是 RESTless 形式的 REST,也根本無法滿足日益複雜的 SPA 應用程式的需求。應用程式越來越像厚客戶端,而厚客戶端問題需要厚客戶端解決方案,而不是被閹割的超媒體客戶端解決方案。
當 GraphQL 發布時,水壩真的崩潰了。
GraphQL 再也不可能比它更不 RESTful 了:你絕對必須要有文件才能理解如何使用使用 GraphQL 的 API。客戶端和伺服器之間緊密耦合。它沒有原生超媒體控制。它提供了 schema,而且在許多方面,感覺很像更新和精簡版的 XML-RPC。
在這裡,我想說:這樣沒關係!
在許多情況下,人們真的很喜歡 GraphQL,如果你正在建立一個厚客戶端風格的應用程式,這很有道理
這個問題的簡短答案是,HATEOAS 並不適合大多數現代 API 的使用情況。這就是為什麼經過將近 20 年,HATEOAS 仍然沒有在開發人員中獲得廣泛採用的原因。另一方面,GraphQL 正像野火一樣蔓延,因為它解決了真實世界的問題。
所以 GraphQL 不是 REST,它不聲稱是 REST,它也不想成為 REST。
但是,直到今天,絕大多數的開發人員和公司,即使他們興奮地在其 API 中添加 GraphQL 功能,仍然繼續使用 REST 這個術語來描述他們正在建立的東西。
不幸的是,voidfunc 可能是對的
你可以盡情地敲打標誌,那場戰役在很久以前就輸了。REST 只是人們用於 HTTP+JSON RPC 的通用術語。
我們將繼續稱呼顯然不是 RESTful 的 JSON API 為 REST,因為現在大家就是這樣稱呼它們的。
儘管我越來越起勁地敲打標誌,50 年後,全球萬能公司仍然會在廣告中徵求開發人員來處理其 RESTful JSON API 的 v138 版 swagger 文件。
無論如何,這裡有一個機會可以向新一代的網路開發人員解釋 REST,特別是統一介面,他們可能從未在其原始上下文中聽說過這些概念,而且他們認為 REST === JSON API。
人們感覺到有些不對勁,也許 REST,真正的、實際的 REST,而不是 RESTless,可能是該問題的答案之一。
至少,REST 背後的想法很有趣,值得作為一般的軟體工程知識來了解。
這裡還有一個更大的元觀點:即使是一群相對聰明的人(早期的網路開發人員),在網際網路的加持下,並且對 REST 這個術語有一個相當明確(如果時而學術性)的規範,也無法在二十年的時間內保持其含義與原始含義一致。
如果我們連這麼明顯的事情都能搞錯,那麼我們還可能在哪些事情上搞錯呢?