REST(Representational state transfer)的四個級別以及HATEOAS介紹


Rest

RES(Representational state transfer):表現層狀態轉移。其實它省略了主語,「表現層」其實指的是「資源」的「表現層」,所以通俗來講就是:資源在網絡中以某種表現形式進行狀態轉移

分解開來:

  • Resource:資源,即數據。比如newsfeed,friends,order等;
  • Representational:某種表現形式,比如用JSON,XML,JPEG等;
  • State Transfer:狀態變化。通過HTTP動詞實現

總結起來就是將數據(資源)轉換成多種格式用以使用,這樣的話就可以針對不同的需求提供不同的數據(資源)格式?非常方便

 

注意:REST是一種架構設計風格而不是標准,如果一個架構符合REST原則,我們就稱它為Restful架構,所謂架構風格就是凌駕於架構之上的一組約束

 

Restful

下面我們來理解一個具體的Restful架構——面向資源的架構(Resource-Oriented Architecture,ROA):

  • 資源是由URI來指定。所謂「上網」,就是與互聯網上一系列的「資源」互動,調用它的URI。
  • 對資源的操作包括CRUD(獲取、創建、修改和刪除)資源,這些操作正好對應HTTP協議提供的GET、POST、PUT和DELETE方法。
  • 通過操作資源的表現形式來操作資源。具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定。
  • 資源的表現形式則是XML或者HTML,取決於讀者是機器還是人,是消費web服務的客戶軟件還是web瀏覽器。當然也可以是任何其他的格式。

 

Rest風格的使用

 

在設計web接口的時候,REST主要是用於定義接口名,接口名一般是用名詞寫,不用動詞,那怎么表達“獲取”或者“刪除”或者“更新”這樣的操作呢——用請求類型來區分。

 

比如,我們有一個friends接口,對於“朋友”我們有增刪改查四種操作,怎么定義REST接口?

 

  • 增加一個朋友,uri: generalcode.cn/v1/friends 接口類型:POST
  • 刪除一個朋友,uri: generalcode.cn/va/friends 接口類型:DELETE
  • 修改一個朋友,uri: generalcode.cn/va/friends 接口類型:PUT
  • 查找朋友,uri: generalcode.cn/va/friends 接口類型:GET

 

上面我們定義的四個接口就是符合REST協議的,請注意,這幾個接口都沒有動詞,只有名詞friends,都是通過Http請求的接口類型來判斷是什么業務操作。

 

舉個反例:generalcode.cn/va/deleteFriends 該接口用來表示刪除朋友,這就是不符合REST協議的接口。

 

一般接口的返回值是JSON或者XML類型的,應該JSON類型偏多

但是,REST開發將對資源的操作不僅僅局限於CRUD(創建、獲取、修改、刪除)的語義(即,將對資源的CRUD操作映射到 GET/POST/PUT/DELETE四個HTTP方法)。如果僅僅局限於這里其實收窄了REST的適用范圍。甚至還有很多僅僅使用了HTTP,而沒有使用SOAP的Web服務API,都自稱是REST風格(RESTful)的API,應該不太正確吧。。?

So,什么才是真正的REST風格,REST的創造者Fielding曾發博:REST APIs must be hypertext-driven!(REST API必須是超文本驅動的!),超文本驅動這個理念變成了一個縮寫詞HATEOAS,這個縮寫詞來自於當初Fielding博士論文中的一句話: hypermedia as the engine of application state(將超媒體作為應用狀態的引擎),其實超文本驅動(Hypertext Driven)的理念才是REST架構風格最核心的理念,也是REST風格的架構達到松耦合目標的根本原因。

 

Restful的四個級別

下面我們用一個不是那么規范的模型來理解restful各個級別:

Level 0 - 面向前台

我們在咖啡店向前台點了一杯拿鐵,這個過程可以用這段文字來描述

{
    "addOrder": {
        "orderName": "latte"
    }
} 

我們通過這段文字,告訴前台,新增一筆訂單,訂單是一杯拿鐵咖啡,接着,前台給我們返回這么一串回復:

{
    "orderId": "123456"
}

假設我們有一張會員卡,我們想查詢一下這張會員卡的余額,這時候,要向前台發起另一個詢問:

{
    "queryBalance": {
        "cardId": "4470313335"
    }
}

查詢卡號為886333的卡的余額,查詢的結果返回來了:

{
    "balance": "0"
}

沒錢…… 
哈哈,沒錢,現在我們要跟前台說,這杯咖啡不要了:

{
    "deleteOrder": {
        "orderId": "123456"
    }
}

Level1面向資源

現在這家咖啡店越做越大,來喝咖啡的人越來越多,單靠前台顯然是不行的,店主決定進行分工,每個資源都有專人負責,我們可以直接面向資源操作。 
比如還是下單,請求的內容不變,但是我們多了一條消息:

/orders
{
    "addOrder": {
        "orderName": "latte"
    }
}

多了一個斜杠和orders,這是什么意思? 
這個表示我們這個請求是發給哪個資源的,訂單是一種資源,我們可以理解為是咖啡廳專門管理訂單的人,他可以幫我們處理所有有關訂單的操作,包括新增訂單、修改訂單、取消訂單等操作。 
接着還是會返回訂單的編號給我們:

{
    "orderId": "123456"
}

下面,我們還是要查詢會員卡余額,這次請求的資源變成了cards:

/cards
{
    "queryBalance": {
        "cardId": "447031335"
    }
}

接下來是取消訂單:

/orders
{
    "deleteOrder": {
        "orderId": "123456"
    }
}

Level2 - 打上標簽

接下來,店主還想繼續優化他的咖啡廳的服務流程,他發現負責處理訂單的員工,每次都要去訂單內容里面看是新增訂單還是刪除訂單,還是其他的什么操作,十分不方便,於是規定,所有新增資源的請求,都在請求上面寫上大大的‘POST’,表示這是一筆新增資源的請求。 
其他種類的請求,比如查詢類的,用‘GET’表示,刪除類的,用‘DELETE’表示,修改用PATCH表示。 
來,我們再來重復上面那個過程,來一杯拿鐵:

POST /orders
{
    "orderName": "latte"
}

請求的內容簡潔多啦,不用告訴店員是addOrder,看到POST就知道是新增,返回的內容還是一樣:

{
    "orderId": "123456"
}

接着是查詢會員卡余額,這次也簡化了很多:

GET /cards
{
    "cardId": "447031335"
}

這個請求我們還可以進一步優化為這樣:

GET /cards/447031335 

直接把要查詢的卡號寫在后面了。 

Level 3 - 完美服務

忽然有一天,有個顧客抱怨說,他買了咖啡后,不知道要怎么取消訂單,咖啡廳一個店員回了一句,你不會看我們的宣傳單嗎,上面不寫着

DELETE /orders/{orderId}

顧客反問道,誰會去看那個啊,店員不服,又說到,你瞎了啊你……后面兩人吵着吵着還打了起來… 
噗,真是悲劇… 
有了這次教訓,店長決定,顧客下了單之后,不僅給他們返回訂單的編號,還給顧客返回所有可以對這個訂單做的操作,比如告訴用戶如何刪除訂單。現在,我們還是發出請求,請求內容和上一次一樣:

POST /orders
{
    "orderName": "latte"
}

但是這次返回時多了些內容:

{
    "orderId": "123456",
    "link": {
        "rel": "cancel",
        "url": "/order/123456"
    }
}

這次返回時多了一項link信息,里面包含了一個rel屬性和url屬性,rel是relationship的意思,這里的關系是cancel,url則告訴你如何執行這個cancel操作,接着你就可以這樣子來取消訂單啦:

DELETE /orders/123456

哈哈,這服務真是貼心,以后再也不用擔心店員和顧客打起來了。 
Level3的Restful API,給使用者帶來了很大的便利,使用者只需要知道如何獲取資源的入口,之后的每個URI都可以通過請求獲得,無法獲得就說明無法執行那個請求。

 看了易懂的例子,下面我們回到原理:

第一級:在架構中引入資源(Resource)的概念。

大多數WS-*服務和POX都只是使用一個URI作為一個服務端口,也只使用一個HTTP方法傳輸數據。這種做法相當於把HTTP這個應用層協議降級為傳輸層協議用,《REST實戰》也一再強調HTTP是一種應用協議而不是傳輸協議。再好一點就是使用多個URI,然而不同的URI只是作為不同的調用入口,與此同時只使用同一個HTTP方法傳輸數據。最常見的錯誤就是在URI中包含動詞,比如URI http://example.com/getOrder?orderId=1234,其實「資源」表示一種實體,所以應該是名詞,動詞應該放在HTTP協議中。而與此同時URI也有可能破壞HTTP GET的安全性和幕等性,比如某個客戶端在http://example.com/updateOrder?id=1234&coffee=latte上執行GET(而不是POST),就能創建一筆新的咖啡訂單(一個資源),按理來說GET請求不能改變服務的任何狀態。

第二級:每一個URI代表一種資源,支持HTTP動詞。

此時使用多個URI的話,需要讓不同的URI代表不同的資源(注意多個URI可能指向同一個Resource,而一個URI不能指向不同Resource。),同時使用多個HTTP方法操作這些資源,例如使用POST/GET/PUT/DELET分別進行CRUD操作。這時候HTTP頭和有效載荷都包含業務邏輯,例如HTTP方法對應CRUD操作,HTTP狀態碼對應操作結果的狀態。我們現在看到的大多數所謂RESTful API做到的也就是這個級別。《REST實戰》的譯者也談到:悟性差的人,理解到CRUD式Web服務就滿足了。而悟性好的人,可以徹底理解超文本驅動,甚至是與REST關系密切的語義網,最終達到 REST開發的最高境界。

第三級:HATEOAS,使用超媒體(hypermedia)作為應用狀態引擎。

根據Roy的嚴格規定,超媒體(hypermedia)是REST的先決條件。任何其他東西不應該自我標榜為REST。要解釋HATEOAS這個概念先要解釋什么是超媒體:我們已經知道什么是多媒體(multimedia),以及什么是超文本(hypertext)。其中超文本特有的優勢是擁有超鏈接(hyperlink)。如果我們把超鏈接引入到多媒體當中去,那就得到了超媒體,因此關鍵角色還是超鏈接。使用超媒體作為應用引擎狀態,意思是應用引擎的狀態變更由客戶端訪問不同的超媒體資源驅動。

讓我們來看個實例,這個響應內容可能略有不同:

GET https://api.example.com/profile

{
  "name": "Steve",
  "picture": {
    "large": "https://somecdn.com/pictures/1200x1200.png",
    "medium": "https://somecdn.com/pictures/100x100.png",
    "small": "https://somecdn.com/pictures/10x10.png"
  }
}

由於在響應中包含了鏈接地址,因此使用該API的客戶端就能夠自由選擇要下載怎樣的信息。這些鏈接告知了客戶端有哪些選擇,並且它們的地址在哪里。因此在這里我們無需同時返回三個不同版本的用戶檔案圖片,我們所做的只是告訴客戶端有三種可用的圖片尺寸可以選擇,並且告訴客戶端能夠在哪里找到這些圖片。這樣一來,客戶端就能夠根據不同的場景,做出符合自身需要的選擇。而且,如果客戶端只需要一種格式的圖片,那就無需下載全部三種版本的圖片了。這樣一來可謂一箭三雕:既減少了網絡負載,又增進了客戶端的靈活性,更增進了API的可探索性。

超媒體的核心概念就是所謂的<link>元素,而這些相互鏈接的資源實際上描述了一個協議,即引導我們達成某個目標的一系列步驟,例如訂購一杯咖啡所需要的點單、付款、取咖啡等等。這就是超媒體的本質:經由資源之間的鏈接,我們改變整個應用的狀態,即超媒體轉換了分布式應用的狀態。需要注意的是,服務器和消費者兩者間交換的是資源狀態的表述,而不是應用的狀態,被轉移的表述中包括了反應應用狀態的鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM