老婆常常喜歡翻看我訂閱的技術雜誌,她總能從她的視角提出不少有趣的問題。html
一個清閒的週日下午,她午覺醒來,又習慣性的抓起這個月的雜誌,饒有興趣地看了起來。程序員
果不其然,看着看着,她又對我發難了,「Restful是什麼呀,老公?是restaurant的形容詞嗎,忽然就以爲好餓了啊……」json
做爲一個合格的程序員,我一直把可以將一項技術講給老婆聽,而且能給她講懂,做爲我已經掌握了這項技術的標準。
若是我直接回答說,「REST就是Representational State Transfer的縮寫呀,翻譯爲中文就是‘表述性狀態轉移’」,那她今晚確定得罰我跪鍵盤。我必須找個合適的機會,把Restful的前因後果給她形象的描述一遍。api
「走,我們去樓下咖啡廳吃個下午茶吧」,我對老婆說。bash
「一個芝士蛋糕,一杯拿鐵,兩條吸管,謝謝」,我對前臺的服務員說,而後咱們找了個角落坐了下來。restful
「剛纔咱們向前臺點了一杯拿鐵,這個過程能夠用這段文字來描述」,說着,我在紙上寫下了這段JSON,雖然她不知道什麼叫JSON,但理解這段文字對於英語專業8級的她,實在再簡單不過。架構
{
"addOrder": { "orderName": "latte" } }
「咱們經過這段文字,告訴前臺,新增一筆訂單,訂單是一杯拿鐵咖啡」,接着,前臺給咱們返回這麼一串回覆:分佈式
{
"orderId": "123456" }
「訂單ID?仍是訂單編號?」
「恩恩,就是訂單編號」
「那咱們就等着前臺喊‘訂單123456的客戶能夠取餐了’,而後就能夠開吃了!」
「哈哈,你真聰明,不過,在這以前,假設咱們有一張會員卡,咱們想查詢一下這張會員卡的餘額,這時候,要向前臺發起另外一個詢問」,我繼續在紙上寫着:工具
{
"queryBalance": { "cardId": "886333" } }
「查詢卡號爲886333的卡的餘額?」
「真棒!接着,查詢的結果返回來了」優化
{
"balance": "0" }
「切,沒錢……」
「哈哈,沒錢,如今咱們要跟前臺說,這杯咖啡不要了」,我在紙上寫到:
{
"deleteOrder": { "orderId": "123456" } }
「哼,這就把訂單取消啦?」
「如今這家咖啡店越作越大,來喝咖啡的人愈來愈多,單靠前臺顯然是不行的,店主決定進行分工,每一個資源都有專人負責,咱們能夠直接面向資源操做。」
「面向資源?」
「是的,好比仍是下單,請求的內容不變,可是咱們多了一條消息」,我在紙上畫出此次的模型:
/orders
{
"addOrder": { "orderName": "latte" } }
「多了一個斜槓和orders?這是什麼意思?」
「這個表示咱們這個請求是發給哪一個資源的,訂單是一種資源,咱們能夠理解爲是咖啡廳專門管理訂單的人,他能夠幫咱們處理全部有關訂單的操做,包括新增訂單、修改訂單、取消訂單等操做」
「Soga…」
「接着仍是會返回訂單的編號給咱們」
{
"orderId": "123456" }
「下面,咱們仍是要查詢會員卡餘額,此次請求的資源變成了cards」
/cards
{
"queryBalance": { "cardId": "886333" } }
「接下來是取消訂單」
「這個我會」,說着,她搶走我手上的筆,在紙上寫了起來:
/orders
{
"deleteOrder": { "orderId": "123456" } }
「接下來,店主還想繼續優化他的咖啡廳的服務流程,他發現負責處理訂單的員工,每次都要去訂單內容裏面看是新增訂單仍是刪除訂單,仍是其餘的什麼操做,十分不方便,因而規定,全部新增資源的請求,都在請求上面寫上大大的‘POST’,表示這是一筆新增資源的請求」
「其餘種類的請求,好比查詢類的,用‘GET’表示,刪除類的,用‘DELETE’表示」
「還有修改類的,修改分爲兩種,第一種,若是這個修改,不管發送多少次,最後一次修改後的資源,老是和第一次修改後的同樣,好比將拿鐵改成貓屎,那麼用‘PUT’表示;第二種,若是這個修改,每次修改都會讓這個資源和前一次的不同,好比是加一杯咖啡,那麼這種請求用‘PATCH’或者‘POST’表示」,一口氣講了這麼多,發現她有點似懂非懂。
「來,咱們再來重複上面那個過程,來一杯拿鐵」,我邊說邊畫着:
POST /orders
{
"orderName": "latte" }
「請求的內容簡潔多啦,不用告訴店員是addOrder,看到POST就知道是新增」,她聽的很認真,理解的也很透徹。
「恩恩,返回的內容仍是同樣」
{
"orderId": "123456" }
「接着是查詢會員卡餘額,此次也簡化了不少」
GET /cards { "cardId": "886333" }
「這個請求咱們還能夠進一步優化爲這樣」
GET /cards/886333
「Soga,直接把要查詢的卡號寫在後面了」
「沒錯,接着,取消訂單」
DELETE /orders/123456
「突然有一天,有個顧客抱怨說,他買了咖啡後,不知道要怎麼取消訂單,咖啡廳一個店員回了一句,你不會看咱們的宣傳單嗎,上面不寫着:
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
「哈哈,這服務真是貼心,之後不再用擔憂店員和顧客打起來了」
「訂單123456的客戶能夠取餐了」,伴隨着咖啡廳的廣播,咱們吃起了下午茶,一杯拿鐵,兩支吸管……
用了大白話,給老婆講明白了RESTful的前因後果,固然,我仍是有些話想說的,只是怕老婆聽完一臉懵逼,沒給她說:
一、
上面講的Level0~Level3,來自Leonard Richardson提出的Richardson Maturity Model:
Level0和Level1最大的區別,就是Level1擁有了Restful的第一個特徵——面向資源,這對構建可伸縮、分佈式的架構是相當重要的。同時,若是把Level0的數據格式換成Xml,那麼其實就是SOAP,SOAP的特色是關注行爲和處理,和麪向資源的RESTful有很大的不一樣。
Level0和Level1,其實都很挫,他們都只是把HTTP當作一個傳輸的通道,沒有把HTTP當作一種傳輸協議。
Level2,真正將HTTP做爲了一種傳輸協議,最直觀的一點就是Level2使用了HTTP動詞,GET/PUT/POST/DELETE/PATCH….,這些都是HTTP的規範,規範的做用天然是重大的,用戶看到一個POST請求,就知道它不是冪等的,使用時要當心,看到PUT,就知道他是冪等的,調用多幾回都不會形成問題,固然,這些的前提都是API的設計者和開發者也遵循這一套規範,確保本身提供的PUT接口是冪等的。
Level3,關於這一層,有一個古怪的名詞,叫HATEOAS(Hypertext As The Engine Of Application State),中文翻譯爲「將超媒體格式做爲應用狀態的引擎」,核心思想就是每一個資源都有它的狀態,不一樣狀態下,可對它進行的操做不同。理解了這一層,再來看看REST的全稱,Representational State Transfer,中文翻譯爲「表述性狀態轉移」,是否是好理解多了?
Level3的Restful API,給使用者帶來了很大的遍歷,使用者只須要知道如何獲取資源的入口,以後的每一個URI均可以經過請求得到,沒法得到就說明沒法執行那個請求。
如今絕大多數的RESTful接口都作到了Level2的層次,作到Level3的比較少。固然,這個模型並非一種規範,只是用來理解Restful的工具。因此,作到了Level2,也就是面向資源和使用Http動詞,就已經很Restful了。Restful自己也不是一種規範,我比較傾向於用」風格「來形容它。若是你想深刻了解Level3,能夠閱讀《Rest in Practice》第五章。
二、
我跟老婆講的時候,用的數據格式是JSON,可是要強調一點,Restful對數據格式沒有限制,就算你用的是XML或者其餘格式,只要符合上面提到的幾個特徵,也算Restful。
三、
關於如何寫出好的Restful API,阮一峯老師已經寫過一篇很是棒的文章:RESTful API 設計指南,這篇文章將指導你寫出優雅的Restful。