本文是「 架構風格:你真的懂REST嗎?
」的補充!
REST全稱是Representational State Transfer,目前普遍接受的中文翻譯為「表述性狀态轉移」!
即使翻譯過來了,你依然有一堆疑問:
- 什麼是「表述性」的?
- 什麼是狀态?
- 什麼是轉移?
- 轉移的是什麼?
是以本文試圖回答如下幾個問題:
- 為什麼要叫REST這個名字?
- 什麼是狀态、資源、表述?
- 以及它們之間有什麼關系?
- 什麼是轉移(Transfer)、變遷(transitions)?轉移什麼?變遷什麼?
為什麼叫REST?
為什麼Fielding博士要取這麼個難以了解的名字呢?其實REST論文的第六章給出了明确的答案:
REST was originally referred to as the "HTTP object model," but that name would often lead to misinterpretation of it as the implementation model of an HTTP server. The name "Representational State Transfer" is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through the application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use.
REST本來是想叫「HTTP object model」的,但是這個名字會給人誤解,讓人誤以為REST是一個HTTP伺服器的實作。叫REST這個名字的目的是為了暗示一個「設計良好的Web應用」應該有怎樣的行為:一個由web頁面組成的網(一個虛拟狀态機),使用者通過選擇連結在應用中前進(狀态變遷),使用者的選擇會導緻下一個頁面(代表應用的下一個狀态)被轉移到使用者端、并被渲染出來以供使用!
Tips:注意上面括号裡的單詞(state transitions),這裡是transitions而不是transfer。transitions表示的是過渡、切換、變遷,比如場景的切換,就是從一個場景到了另一個場景。這裡是從一個狀态切換到了另一個狀态。REST中文文檔裡,還是将其翻譯成了「轉移」,應該是不正确的!
網上很多讨論REST的文章或文章,關注的點有兩個:
- Representational應該怎麼翻譯?是「表述」還是「表述性」
- Transfer應該怎麼翻譯?是「轉移」還是「傳輸」
而從上面這段話,你會發現,重點既不是Representational、也不是Transfer,而是State!你有沒有覺得上面所提到的State和你平時所了解的State有差異?或者說比較違和?
我們都知道,要保證服務端的伸縮性,就要確定服務端是無狀态的!如果是「無狀态」的,那麼為什麼有「狀态的變遷」呢?難道REST沒有伸縮性?顯然不是,要知道,Web可是現今伸縮性最好的系統!
是以這裡所指的State與我們平時所說的State不是一個概念!這裡的State是「應用狀态」,我們所說的State是「資源狀态」(這裡所說的資源和REST中所指的資源也是不一樣的,下面會說到)!
先說應用狀态,在上文中,可以看到。應用狀态指的是一個個的Web頁面!Web頁面上有連結,你點選連結後,這個連結所對應的「應用狀态」會從伺服器「轉移」到用戶端,渲染出來,展示給你。你就「切換」到了下一個「應用狀态」!
是以「State Transfer」指的是:「應用狀态」從服務端「轉移」到了用戶端,導緻用戶端的「應用狀态」從目前狀态「變遷」到了下一個狀态!
資源與表述
在解釋「資源狀态」之前,要先來解釋一下什麼是「資源」?什麼是「表述」?
早期URI設計時,「資源」表示的是「文檔」!它假設網際網路裡轉移的都是文檔!現在看來,顯然不是!REST對「資源」進行了抽象!
一般我們對資源的了解是「可以在網際網路裡轉移的任何内容」,比如:網頁、圖檔、視訊等!但實際上,REST論文中給出的定義和我們平常所了解的「資源」差異還是很大的!
REST論文中給出的解釋:
The resource is not the storage object. The resource is not a mechanism that the server uses to handle the storage object. The resource is a conceptual mapping -- the server receives the identifier (which identifies the mapping) and applies it to its current mapping implementation (usually a combination of collection-specific deep tree traversal and/or hash tables) to find the currently responsible handler implementation and the handler implementation then selects the appropriate action+response based on the request content.
資源不是存儲對象!也不是伺服器處理存儲對象的機制!資源是一個概念上的映射關系:伺服器接收到标示符(這些标示符标示了這個映射關系),将其應用到目前的映射實作上(一般是特定集合【深度周遊的樹和/或哈希表】的組合)來找到目前負責處理該請求的處理器、這個處理根據請求内容選擇合适的動作+響應
我用一段僞代碼來解釋一下!
var mappingImpl = {'/pathA':handlerA,'/pathB':handlerB,'/pathC':handleC,...}
mappingImpl.get('/pathB').handle(req);
- mappingImpl就是映射實作,包含了所有URI到處理器實作的映射關系!
- '/pathB':handlerB這個映射關系就是「資源」!
- mappingImpl.get('/pathB')就是根據URI擷取到負責處理該請求的處理器
- handle(req)就是根據請求内容來選擇合适的動作+響應
按照這個定義的話,實際上REST中所指的「資源」和我們平常所指的「資源」根本不是同一個東西:
- REST中的資源指的是URI到針對這個URI的處理器之間的映射關系
- 我們平常所指的資源,對應到REST中實際上是這個處理器處理後的傳回結果
你會發現,REST中的資源是個「動态」的東西,而我們所說的資源是個「靜态」的東西,或者說就是個類型多樣化的「文檔」而已!
用僞代碼表示的話就是:
var resourceMap = {'/pathA':resourceA,'/pathB':resourceB,'/pathC':resourceC,...}
resourceMap.get('/pathB');
實際上,現代Web應用中,絕大部分URI标示的都是動态的内容!是以在這一點上,REST對資源的定義更加的準确!
你可能會說,這兩者的差別可能不是那麼大,因為無論資源指的是關系還是處理結果,我們最終都是看到的是一樣的内容!你确定嗎?
假設資源就是我們平常所了解的「傳回結果」:
- 上面說到的,傳回内容中包含了連結,用戶端可以通過連結變遷到下一個狀态,連結和資源怎麼同時傳回?
- 如果URI指定的那個資源不存在的話,應該傳回什麼呢?
resourceMap.getOrDefault('/pathD','這裡該傳回什麼?');
傳回「空資源」?那什麼是「空資源」,如何表示「空資源」?null嗎?那用戶端又如何處理這個特殊的null呢?或者說是一個「Null資源」?「Null資源」是資源嗎?既然是沒有資源,那還叫資源嗎?是不是很拗口?
REST通過表述來解決這個問題:
- 表述就是處理器傳回的結果
- 包含了連結(操作)和内容(響應)
- 如果沒有内容,那麼傳回的就是一個沒有内容的表述
mappingImpl.getOrDefault('/pathD',defaultHandler).handle(req);
// 如果找不到對應的處理器就用預設處理器處理。
//如果找不到請求内容對應的響應,就傳回一個沒有内容的表述
上面所說的Web頁面就是一種表述,也就是說表述是「應用狀态」!
是以對REST的了解應該是:通過連結,以表述的方式,将應用狀态從服務端轉移到用戶端!
解釋完資源和表述,最後來解釋一下「資源狀态」!
資源狀态
很多關于RESTful的博文,主要講的是:
- URI要怎麼設計
- 要用POST建立資源
- 要用PUT更新資源
- 要用DELETE删除資源
- 要用GET擷取資源
以User為例:
GET /users // 擷取使用者清單
GET /user/1 // 擷取ID為1的使用者資訊
POST /user // 建立使用者
PUT /user/1 // 更新ID為1的使用者資訊
DELETE /user/1 // 删除ID為1的使用者資訊
這裡的POST,PUT,DELETE改變的是「資源狀态」!和上面所說的「應用狀态」是兩回事!很多地方都沒有明确的區分開來!
記得當初看JVM的時候,也有類似的情況!JVM中有三個棧,虛拟機棧、操作數棧和本地方法棧。很多地方都直接叫做棧,這就導緻了了解的混亂!前面說一個方法的調用就是将對應的棧幀入棧;後面又說指令的執行,對應的操作數入棧。實際上前一個棧指的是虛拟機棧,後一個棧指的是操作數棧!
在這裡情況類似,狀态沒有區分開,也會導緻了解的混亂!前面說「狀态的轉移」,這裡又說「狀态的改變」!一個是「應用狀态的轉移」,一個是「資源狀态的改變」!
如果按照上面對資源的了解的話,這裡的狀态其實不應該叫「資源狀态」,而應該是「内容狀态」!
REST完整流程
下面以User的CRUD為例,來說明一下REST的完整流程:
- 通路/users連結,服務端找到該URI對應的處理器,對請求進行處理,将包含了User清單内容的表述轉移到用戶端
- 用戶端展示該表述。表述中包含了到新增User頁面的連結、到編輯User頁面的連結、删除User的連結等操作連結
- 使用者點選到新增User頁面的連結,服務端轉移新增User的表述。用戶端「應用狀态」變遷到新增User頁面
- 使用者填寫資訊,點選送出按鈕。伺服器處理該請求,将一條新的使用者資訊添加到資料庫,導緻了User「内容狀态」變化。同時将新的包含了User清單内容的表述轉移到用戶端
- 用戶端「應用狀态」又進入到了User清單頁面
- ......