自2000年Roy Fielding提出REST架構之後,時至今日,不管是前端或後端,多少都看過或略知REST的概念,不少Web服務也採用REST風格設計和實現,然而Roy Fielding曾表示,沒有Hypermedia就不是在做REST。這是怎麼一回事?

Richardson成熟模型Level 0與Level 1

在2000年Roy Fielding提出的論文在介紹中提及,WWW能興起,大多歸功於符合分散式Hypermedia系統的需求,2014年InfoQ的訪談〈Roy Fielding on Versioning, Hypermedia, and REST〉,也提到「hypermedia as the engine of application state」並不是選項,REST必須實現這個約束,否則就不是在做REST。

2008年,Leonard Richardson在QCon Talk中,也曾經根據當時實現RESTful時的URI、HTTP、Hypermedia三個約束,建立了一個堆疊模型,並將這三個約束的採用程度,分成了四個等級,用以評估Web服務設計的好壞與否,而Martin Fowler在2010年〈Richardson Maturity Model〉曾經進一步詮譯這個模型,稱為Richardson成熟模型。

在Richardson成熟模型LEVEL 0的應用程式當中,會使用單一URI與一個HTTP方法,基本上,僅單純使用HTTP作為傳輸協定,服務的URI只是接收請求與進行回應的端點,HTTP方法只用來發起請求,至於請求相關細節,例如想進行的動作、處理請求時必要的資料等,全部包含在發送過去的文件中,像是XML、JSON等其他(自訂)格式,回應中也以某個格式包含了請求操作後的結果。

我們可以把Level 0,想像成只使用/messages來接受POST請求的應用程式,想要查詢某個訊息、全部訊息、刪除訊息、修改訊息等,都在POST本體中使用某格式的文件,以某個屬性來指定,就像是個連線程式,有指定的連線位置,傳送指定格式的封包。而這個階段的代表為SOAP、XML-RPC等。

至於Level 1,是使用多個URI與一個HTTP方法,當中的URI代表某項資源,像是/show_message、/create_message、/update_message等都是資源,HTTP方法僅用來發起請求,至於請求細節會由請求本體來提供,例如,在請求/show_message 這項資源時,若包含all請求參數,表示顯示全部的訊息,若是"id=1"這類請求參數,代表顯示指定的訊息。

REST與HATEOAS

現今應用程式開發者認知中的REST,其實多是屬於Richardson成熟模型中的Level 2,使用多個URI、多個HTTP方法,並善用HTTP回應狀態碼來代表操作結果。而其中的URI用來代表資源,像是/messages、/messages/1等就是資源代表;HTTP方法則是動詞,也就是打算進行的動作,例如GET /messages取得全部訊息,GET /messages/1取得指定訊息,POST /messages新增訊息、DELETE /messages/1刪除指定訊息等。

在善用HTTP方法作為動詞這部份,可以參考先前專欄〈重新認識HTTP方法〉。多數接觸或應用過REST架構的開發者,多半停留在Level 2的概念,僅採用了URI及HTTP作為約束,在資源間交換訊息時,現今常使用易於閱讀理解的JSON格式,由於REST是個風格而不是規範,為了有更一致的屬性設定等約束,建議採用JSON API之類的(非正式)格式規範。

Level 3更進一步地在Level 2的約束基礎,加上Hypermedia,資源除了URI作為識別之外,還必須能表示出本身接受的操作,以及與其他資源間的關係──後者有點類似HTML頁面超鏈結的概念,你可以從目前HTML頁面得知有哪些頁面能夠前往,但Level 3不是只有超鏈結,除了知道如何前往其他資源,還要能夠知道它是什麼、如何對它操作等。

Roy Fielding是在其論文第五章REST中,提到hypermedia as the engine of application state,簡稱HATEOAS,而Leonard Richardson的模型中,達到Level 3成熟模型的應用程式,必須支援HATEOAS。

HATEOAS與HAL

按照Roy Fielding的觀點,沒有HATEOAS約束,就不是REST,然而,Leonard Richardson也說過,一開始就要討論HATEOAS的概念是困難的,因此他才從HTML與URI開始,開展了他對成熟模型的區分。

HATEOAS是個概念,實際上,如何從一個資源得知其他的資源?該採用哪個格式?格式中該採用哪些識別名稱?這需要有實作規範,而HAL是實作規範之一,採用特定的JSON格式、ATOM (RFC 4287) 鏈結語法概念,來描述某些協議等(JSON API雖然也可包含資源關係,然而畢竟不像HAL就是為了支援HATEOAS)。

一個HAL的回應,會像下列這樣:

{"_embedded": {
"messageList": [{
"text": "msg1",
"_links": { "self": { "href": "http://localhost:8080/messages/1"}}}]},
"_links": {
"self": {"href": "http://localhost:8080/messages"}
}
}

上述的_embedded代表著目前資源內嵌的其他資源,_links表示相關資源的URI,每個鏈結會有個屬性來描述其作用或操作。例如self屬性描述了資源本身的URI,常見的屬性描述,還有next、previous、deprecation等。因為回應本身就包含了鏈結、參數等相關的資訊,必要的話,客戶端在實作上,可以實現出自動從資源探尋到另一個資源的作法,若資源變動了,由於回應有著一致的格式與描述,客戶端可以自動因應變化。

相較之下,不支援HATEOAS應用程式,必須在文件上記錄有哪些端點、操作方式、資源間關係等,資源提供者與消費者之間,也有著極為緊密的關係,資源若有所變動,文件必須跟著修改,客戶端也必須依文件作出對應的調整。

HATEOAS不是什麼?

現今有些程式庫(像是Spring HATOAS)可以支援HATEOAS,方便開發者產生相關的URI、描述屬性等,如果開發者曾經在文件上,記載著資源的操作相關描述,以及與其他資源間的關係,而且,常因為資源變動,致使客戶端與文件都須做出修改,就可以考慮將成熟度提至Level 3模型。

某些程度上,雖然從回應中,可以大致得知資源的使用方式,然而,並不是採用HATEOAS概念,就不用寫文件了,HATEOAS當中,雖然可以呈現的資源間的關係及操作描述,並不包含商務邏輯上的語意,因為,它考量的是,客戶端可以依這些描述,自動因應資源的變化,而不是客戶端可以在沒有文件的狀況下,理解資源能達成哪些商務邏輯。

所以,文件還是必要的!這也是〈Why I Hate HATEOAS〉談到的,有些開發者聲稱使用HATEOAS就可以不用寫文件,這是很可笑的行為。如果真的懶得寫文件,更合理的方式是使用Swagger之類的工具,自動產生相關文件甚至是測試介面。

作者簡介


Advertisement

更多 iThome相關內容