從消費者角度評估RestFul的意義

相關博文:html

從消費者角度評估RestFul的意義git

SpringBoot 構建RestFul API 含單元測試    github

  REST是目前業界至關火熱的術語,彷佛發佈的API不帶個REST前綴,你都很差意思和別人打招呼了。 然而大部分號稱REST的API實際上並無達到Richardson成熟度模型的第三個級別:Hypermedia。 而REST的發明者Roy Fielding博士更是直言「Hypermedia做爲應用引擎」是REST的前提, 這不是一個可選項,若是沒有Hypermedia,那就不是REST。(摘自Infoq對Fielding博士的第二段訪談)json

什麼是Hypermedia?

  那究竟什麼是Hypermedia? 採用Hypermedia的API在響應(response)中除了返回資源(resource)自己外,還會額外返回一組連接(link)。 這組連接描述了對於該資源,消費者(consumer)接下來能夠作什麼以及怎麼作。api

舉例來講,假設向API發起一次get請求,獲取指定訂單的資源表述(representation),那麼它應該長得像這樣:app

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 05 Jun 2015 02:54:57 GMT

{
    "tracking_id": "123456",
    "status": "WAIT_PAYMENT",
    "items": [
        {
            "name": "potato",
            "quantity": 1
        }
    ],
    "_links": {
        "self": {
            "href": "http://localhost:57900/orders/123456"
        },
        "cancel": {
            "href": "http://localhost:57900/orders/123456"
        },
        "payment": {
            "href": "http://localhost:57900/orders/123456/payments"
        }
    }
}

 

  • 理解連接中的「self」的消費者知道使用get方法訪問其「href」的uri能夠查看該訂單的詳細信息
  • 理解連接中的「cancel」的消費者知道使用delete方法訪問其「href」的uri能夠取消該訂單
  • 理解連接中的「payment」的消費者知道使用post方法訪問其「href」的uri能夠爲該訂單付款

這看起來頗有趣,然而這對API的消費者來講有什麼好處呢?post

再也不揣測如何組合使用API

  不知道在你的開發生涯中有沒有遇到過這樣的事情:單元測試

有一天,產品經理跟我說,咱們要和某某酒店集團對接,在線銷售它們的酒店,這是他們的聯繫人和詳細的API說明文檔。 API說明文檔真夠詳細,有好幾十頁,憑着豐富的行業經驗,我知道我須要找到其中的哪些API來實現基本的業務場景。 幾天後,我實現了大部分的API集成,如今能夠預訂酒店了,訂單已經在對方的測試環境生成,大功告成。 等等,這個「添加訂單財務信息」的API是幹嗎的?在和對方的聯繫人聯繫後,被告知「沒什麼用」,好了,真的大功告成了,上線!

兩週後,咱們的API使用權限被對方關閉了,緣由是「全部的訂單都沒有財務信息,沒法確認對帳」。  
「等等,那誰誰誰不是說這個API沒用嗎?」
「噢,他已經離職了,大家若是要恢復使用,儘快完成這個API的集成吧」  
「我。。。」

固然,這裏面還有許多別的因素,可是消費者的開發人員每每很難將業務場景和實現業務場景的API聯繫起來。 他們經常面對是:測試

  • 不熟悉的業務場景
  • 一套對單個API的做用描述詳細,但缺少API之間聯繫的文檔。

Hypermedia帶來的API自描述特性,使用連接的方式提示接下來作什麼和怎麼作,正好能夠緩解這樣的窘境。 若是API服務方能夠提供測試環境供消費者測試,那麼開發人員能夠實際動手探索業務場景的銜接,這時再配合API文檔狀況就好多了。 回到酒店訂單的例子,若是是這樣,我可能就不會挨批了:spa

// 預訂後,提示確認訂單,那麼不熟悉爲何要確認以及不確認的後果的同窗就能夠想到去問啦
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "cancel": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "acknowledge": {
            "href": "http://zhaodaisuo.com/orders/123456/payments"
        }
    }
}
// 確認後,提示添加財務信息,不熟悉的同窗就能夠問了,然而我真的已經問了呀。。。。
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "billing": {
            "href": "http://zhaodaisuo.com/orders/123456/bill"
        }
    }
}

今後與API版本說再見

  不知道在你的開發生涯中有沒有遇到過這樣的事情:

有一天,產品經理跟我說,咱們要實現一個新功能blablabla,可是依賴的API版本太老了,這是他們的聯繫人。
「你好呀,請問咱們須要這個信息,可是如今1.3的版本中沒有提供,有什麼辦法嗎?」
「你能夠升級到2.1的版本就有了」
「那這個版本是否是向後兼容的啊?咱們用了其中不少接口哦,我不想其它的集成點出問題」
「那固然,放心吧」

結果固然是個悲傷的故事,「你給我過來,我保證不打死你」。 API的發佈方也須要增長新功能,API自身也會隨着需求變化,因而產生了版本號 

http://www.zhaodaisuo.com/api/v1.2

然而一套API通常會包括多個API,爲整套API版本化的粒度太粗了。 一旦消費者但願得到其中某個API的新特性,他/她只能選擇全盤升級並仔細測試或者爲每一個集成點配置單獨的uri。 這都不夠好,而Hypermedia能夠改變這種局面。 因爲提供了連接來告訴消費者資源的uri,相對「傳統」的REST API,uri變成了一種弱耦合, Hypermedia API只須要公佈少許入口uri就能夠了。好比,以以前酒店訂單的例子,只需發佈

http://www.zhaodaisuo.com/orders

後續的確認、財務信息的uri是在實際API調用的時候拿到的,無需事先準備。 消費者和發佈者之間的強耦合實際上只剩下入口uri和服務契約(解釋資源的含義), 當服務契約新增或是發生破壞性的變化時(例如修改了或刪除了參數),只須要在資源表述中增長新的連接。

{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "ACKNOWLEDGED",
    "_links": {
        "self": {
            "href": "http://zhaodaisuo.com/orders/123456"
        },
        "billing": {
            "href": "http://zhaodaisuo.com/orders/123456/bill"
        },
        "billing-v1.1": { //billing發生了破壞性變化
            "href": "http://zhaodaisuo.com/orders/123456/bill/v1"
        },
        "coupon": { //新增了優惠券抵用的契約
            "href": "http://zhaodaisuo.com/orders/123456/coupon"
        }
    }
}

這樣版本化的粒度就下移到了服務契約的級別,這時消費者就靈活多了,只要按需修改對應的集成點就好了。

完全與API的內部實現解耦

  不知道在你的開發生涯中有沒有遇到過這樣的事情:

有一天,產品經理跟我說,咱們依賴的一個API發佈者通知我,如今判斷訂單是否可以用優惠券的條件變化,這是他們的聯繫人。
「你好呀,請問如今訂單可否用優惠券的判斷條件有什麼變化?」
「原來,大家能夠經過訂單的狀態來判斷,如今還須要結合訂單的來源,參加秒殺活動的訂單不能使用優惠券」
「好吧。。。」

因而我在集成代碼中作了以下修改:

 if (order.status().equals("WAIT_PAYMENT")) {
    if  (order.source().equals("miaosha")) {
        couponButtonEnabled = false;
    } else {
       //....
    }
} else {
   //...
}

好糾結,就不能把API設計成這樣嗎?

{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_PAYMENT",
    "source": "normal",
    "_links": {
        "coupon": {
            "href": "http://zhaodaisuo.com/orders/123456/coupon"
        }
    }
}
{
    "tracking_id": "123456",
    "Hotel": "A ZHAO DAI SUO",
    "status": "WAIT_PAYMENT",
    "source": "miaosha",
    "_links": {} //秒殺來源的訂單不返回含優惠券連接的資源表述。
}

 

 這樣客戶端代碼就簡單了,依賴於抽象的業務場景,而不是依賴於具體的實現

if (order.containsLink("coupon")) {
    couponButtonEnabled = true;
} else {
    couponButtonEnabled = false;
}

 

 
轉自: http://hippoom.github.io/blogs/value-of-hypermedia-from-client-perspective.html
相關文章
相關標籤/搜索