突然讨厌做前端,讨厌代码_不要讨厌HATEOAS
突然討厭做前端,討厭代碼
或我如何學會不再擔心和愛HATEOAS
REST已成為實現Web服務的事實上的解決方案,至少已成為一種流行的解決方案。 這是可以理解的,因為REST在使用HTTP規范時提供了一定程度的自我文檔。 它經久耐用,可擴展,并提供了其他一些理想的特性。
但是,許多所謂的RESTful服務都沒有實現HATEOAS (作為應用程序狀態引擎的超媒體),這會使Roy Fielding晚上 忙 起來 (如果您認為介紹不好,請閱讀評論部分 )。 這是一個不幸的趨勢,因為包括超媒體控件提供了很多優勢,特別是在客戶端與服務器解耦方面。
本文是一個分為兩部分的系列文章的第一篇,將介紹控制REST的底層實現細節和設計方面的問題。 我們將討論在RESTful服務中實現HATEOAS值得付出額外努力的原因,因為您的服務面臨著不斷變化的業務需求。
第二部分將于3月28日發布,它將是使用Spring-HATEOAS實現HATEOAS服務的實時代碼示例。 在我即將于2016年3月2日星期三在堪薩斯城Spring用戶小組的主題為“ 我如何學會停止照顧并開始愛上HATEOAS ”的演講中,您還可以看到其中的一些概念。
REST,成功實現建筑約束的成功典范
作為一名開發人員,我不得不經常沮喪地學習如何在高位建筑師對我施加的約束下工作。 自從最近向架構師過渡以來,我現在可以定義自己的約束,并盡自己的力量繼續痛苦的循環。 但是,在研究本文時,我了解了REST體系結構中經過深思熟慮的約束是如何使其成為Web服務世界的先驅。 苦難的循環至少在這次減少了。
Roy Fielding 在2000年的博士論文中定義了控制REST的六個主要建筑風格約束。 我將詳細介紹其中的五個。 第六,按需編碼(可選)將不涉及。 五個幸運的樣式約束是:客戶端-服務器,無狀態,可緩存,統一接口和分層體系結構。
1.客戶端-服務器
第一種樣式約束是客戶端-服務器分離。 具有諷刺意味的是,這是當開發人員選擇不實施HATEOAS時受影響最大的約束。
關注點分離是好的系統設計的基本原則之一。 在REST和Web服務的上下文中,這種關注點分離在可伸縮性方面具有一些好處,因為RESTful服務的新實例也不需要處理客戶端的拆包。
真正的好處,就像在任何時候都實現關注點分離約束一樣,盡管允許獨立發展。 客戶端處理演示,服務器處理存儲。 這種分離意味著對服務器的每次更改都不需要對客戶端進行更改(并且不需要協調兩者之間的發布),反之亦然。
在本文的后面,我們將更詳細地介紹如何不實施HATEOAS來模糊客戶端和服務器之間的界限。
2.無狀態
如果您要問開發人員RESTful服務的關鍵特征是什么,您可能會首先收到的答復是它是無狀態的。 這是一種流行的響應,因為無狀態在REST最令人希望的兩個特性中發揮著核心作用:持久性和可伸縮性。
在這種情況下,無狀態意味著每個請求都包含服務器接受或拒絕請求所需的所有信息,并且服務器不需要檢查會話狀態即可確定請求的有效性。 這將導致持久性,因為客戶端不再綁定到特定的服務實例。 如果客戶端正在與實例“ A”進行對話并且故障,負載均衡器可以將客戶端重定向到另一個可用實例,沒有人是明智的。
另一個好處是可伸縮性,這是因為服務器資源不會因存儲用戶狀態而消耗(如果服務足夠流行,則可能會消耗大量資源)。 它還可以響應流量的激增,加快附加服務實例的旋轉速度。 也就是說,要實現該功能需要高度的DevOps成熟度。
3.可緩存
第三個樣式約束是請求可以是可緩存的。 在這種情況下,可緩存性是指客戶端緩存請求的能力。 這與像Redis這樣的服務器托管的緩存相反,盡管這是在以后的約束中啟用的。 緩存客戶端請求是每個主流瀏覽器中都已實現的功能,可通過使用http頭激活該功能,如下圖所示(緩存控制)。
圖片來源:https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-CN
使客戶端緩存請求的好處是不需要服務器重新提供對未更改且經常訪問的資源的響應,從而減少了服務器負載。 同樣,由于瀏覽器比從服務器更快地檢索本地緩存的響應,因此可以改善客戶端的感知性能。
4.統一的界面
RESTful服務的端點是資源。 狀態的變化是通過操縱這些資源而發生的。 發送到這些資源的消息是自描述的,超媒體是應用程序狀態的引擎(最后一個約束聽起來很熟悉)。
在下面的Richardson成熟度模型部分,我們將逐步介紹在服務上實現這四個約束的外觀。
5.分層架構
像食人魔和洋蔥一樣,REST體系結構也有層次。 RESTful服務中的分層體系結構是通過通過它發送的消息具有自描述性而實現的,并且每一層都無法從接口看到到下一層。
當我提交在Netflix上觀看電影的請求時,無論我使用什么客戶端,都將發送GET請求。 該請求可能會到達路由服務。 看到這是一個GET請求(即檢索),然后該路由服務可以將該請求發送到服務器緩存。 該緩存可以檢查其是否具有與請求的查詢匹配的未過期資源。 在我的請求可以實現之前,這可能會持續進行幾層甚至在Netflix體系結構中的某些區域。 所有這些路由和重定向都可能發生,因為REST消息是自描述的。 只要層可以理解HTTP,它就可以理解它收到的消息。
理查森成熟度模型
因此,我們已經涵蓋了控制REST的六種主要架構樣式約束中的五種。 現在,讓我們仔細看看第四個樣式約束,即統一界面,如先前所承諾的。 統一的接口定義了RESTful服務的許多“外觀”,在該接口上定義了以下端點:GET:/ users / bob。 這也是定義HATEOAS的地方,這就是本文的重點。 為了使這些約束的影響可視化,并了解許多RESTful服務的不足之處,我將以有用的Richardson成熟度模型(RMM)為指南。
POX的沼澤
這是RMM上的級別0。 在這里,不能真誠地將服務描述為RESTful。 客戶與之連接的端點不是資源,我們在請求中未使用正確的HTTP動詞,并且服務器未使用超媒體控件進行響應。 我們都已經在這樣的服務上進行了工作,確實有可能(盡管可能不太可能)這種服務易于使用和維護……但是無論它是否絕對不是RESTful的。
通過RMM時,我們將通過通過亞馬遜等在線零售商訂購電視來觀察REST中統一接口約束的實現如何影響服務器與客戶端之間的交互。
在這里,我們看到了級別0的交互:
POST: viewItem {“id”: “1234” } Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00 } POST: orderItem {“id” : 1,“items” : [“item” : {“id” : 1234}] } Response: HTTP 1.1 200 {“id” : 1,“items” : [“item” : {“id” : 1234}] }資源資源
在RMM的這一級別上,我們正在實現統一接口的前兩個約束。 我們正在通過URI(/ items / 1234,/ orders / 1)識別與我們交互的資源,而我們如何通過處理這些資源來與服務交互。
在向我們的服務發送請求時,為我們的每個資源分配一個專用的端點而不是單個端點,可以為客戶與之交互的實體提供更多的標識。 它還提供了收集分析數據的機會,我們的客戶如何與我們的服務交互。 熱圖可以更輕松地顯示正在請求哪些資源以及該資源中的特定實體。
POST: /items/1234 {} Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00 } POST: /orders/1 {“item” : {“id” : 1234} } Response: HTTP 1.1 200 {“id” : 1,“items” : [“item” : {“id” : 1234}] }因此,現在我們要使用資源終結點而不是所有請求都將通過的匿名終結點。 但是,我們與服務互動的性質尚不清楚。 當我們發布到/ items / 1234時,是在創建一個新項目還是要檢索? 當我們發布到/ orders / 1時,是在更新現有實體還是創建一個新實體? 在發送請求時,客戶端尚不清楚這些交互。
HTTP
到目前為止,我們一直主要使用HTTP作為客戶端與RESTful服務進行交互的傳輸機制。 在此級別,我們將開始使用已定義的HTTP規范。 到目前為止,我們已經使用POST提交了所有請求,現在我們將開始使用更合適的HTTP動詞(方法類型)。 這不是一條單向的街道,但是我們的服務器還會響應更合適的狀態代碼,而不是對每個成功的請求都響應200狀態代碼。
下表列出了RESTful服務通常實現的動詞以及對這些動詞的一些約束。 如果您不熟悉“冪等”一詞(作者曾經),那么請知道,這意味著當執行次數大于零時,執行請求的副作用是相同的。
GET調用應始終返回相同的項目列表。 DELETE請求應該刪除元素,但是后續的DELETE請求應該不會改變服務器的狀態。 注意,這并不意味著響應總是必須相同。 在第二個示例中,第二個DELETE請求可能返回錯誤響應。 安全意味著該操作不會影響服務器的狀態。 GET僅用于檢索,它不會更改正在檢索的資源的狀態。 但是,PUT請求可能導致狀態更改,因此不是安全動詞。
| 安全 | 不安全 | |
| 勢力 | GET,HEAD,TRACE,選項 | 刪除,放入 |
| 不占優勢 | 開機自檢 |
當我們開始在交互中使用正確的HTTP動詞和狀態代碼時,這就是交互的外觀:
即使不深入了解HTTP規范,客戶端與服務器之間的交互也變得更加清晰。 我們正在從服務器獲取項目; 我們正在服務器上放置一些東西。 有一些字幕可以幫助您理解HTTP,了解PUT意味著修改會告訴開發人員一個訂單已經存在,而我們正在修改它,而不是創建新訂單(可能是POST請求)。
理解HTTP狀態代碼還將使開發人員對服務器如何響應來自客戶端的請求有更多的了解。 雖然我們的服務器仍對初始GET請求返回適當的200響應,但服務器現在發送的PUT請求響應為226(已使用IM),這意味著僅返回已更改資源的增量。 如果您在“資源”部分下查看向訂單添加商品的響應,則服務器將返回訂單ID和商品列表。 在此響應中,僅返回添加到訂單中的項目。 如果訂單中已經有其他項目,則它們也將在“資源”響應中返回,但在此響應中將其省略。
或者,如果不存在ID為1234的項目,則HTTP已經定義了正確的響應,而不是返回空的響應正文或某種錯誤消息。 你能猜出來嗎?
GET: /items/1234 Response: HTTP 1.1 404超媒體控件
以上為電視下訂單的場景為實現超媒體控件帶來好處提供了一個很好的用例。 在此情況下,我已經假設用戶已經有一個ID為“ 1”的預先存在的訂單,但是可能并非總是如此。
在不使用HATEOAS將狀態應用程序傳達給客戶端的情況下,客戶端必須足夠聰明才能確定用戶是否有未結訂單,如果有,則確定該訂單的ID。 這將導致工作重復,因為確定用戶狀態的業務邏輯現在同時存在于客戶端和服務器上。 隨著業務的變化,客戶端和服務器之間將具有依賴關系來確定用戶訂單的狀態,客戶端和服務器代碼都將發生更改,并且需要協調兩者之間的發布。 HATEOAS通過通過返回的鏈接告知客戶端狀態(即客戶端下一步可以做什么)來解決此問題。
GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“link” : {“rel” : “next”,“href” : “/orders”}} } POST: /orders {“id” : 1,“items” : [{“id” : 1234}] }Response: HTTP 1.1 201: {“id” : 1,“items” : [{“id” : 1234} ] links : [{“rel” : “next”,“href” : “/orders/1/payment”}, {“rel” : “self”,“href” : “/orders/1”}] }可以省去確定用戶是否有有效訂單的相對簡單性,因為它不夠復雜,不足以證明在服務器端實施HATEOAS然后開發可以解釋服務產生的超媒體控件的客戶端所花的時間(都不其中是微不足道的)。 也就是說,該示例也非常簡單,僅代表客戶端和服務器之間的一種交互。
死亡,稅收和變化,HATEOAS就是這樣
開發人員知道,“唯一可以確定的就是死亡和稅收”的成語是錯誤的,第三個可以肯定的是:變化。 任何開發的應用程序都將在其生命周期內發生變化; 添加了新的業務需求,修改了現有的業務需求,并一起刪除了一些業務需求。
雖然我不希望HATEOAS成為銀彈,但我確實相信HATEOAS是為數不多的技術之一,隨著遇到現實問題,其收益會增加。 以下是三個用例的示例,這些用例結合在一起以及可以想象的其他用例,為您為什么要在RESTful服務中實現HATEOAS奠定了有力的案例。
用例1:管理員和普通用戶通過同一客戶端進行交互
普通用戶和管理員都使用同一客戶端與服務進行交互。 在這種使用情況下,普通用戶將只能在/ items資源上執行GET,但是管理員也將具有PUT和DELETE特權。 如果我們在Richardson成熟度模型(HTTP)的第2級上停止,我們將需要讓客戶端了解用戶所具有的特權類型,以便正確地向用戶呈現接口。
使用HATEOAS,可能就像客戶端呈現服務器發送的一些新控件一樣簡單。 這是請求中的不同之處。 另外,我們可能不希望管理員下達訂單:
Request: [Headers] user: bob roles: USER GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “next”,“href” : “/orders”}] } }Request: [ Headers ] user: jim roles: USER, ADMIN GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”},{“rel” : “delete”,“href” : “/items/1234”}] } }用例2:管理員不再可以刪除
業務需求發生變化,管理員不再能夠刪除項目。 在上一個用例中,很可能會說不需要更改客戶端(例如,管理員用戶需要使用表單來修改項目的字段),但是刪除DELETE動詞絕對可以完成而無需更改客戶。
隨著HATEOAS服務不再返回DELETE鏈接,客戶端將不再將其顯示給管理員用戶。
Request: [Headers] user: jim roles: USER, ADMIN GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”}] } }用例3:用戶可以出售自己的商品
企業現在要求用戶具有銷售自己的用戶商品的能力。 這個用例比前兩個更為實際,開始顯示出客戶端上業務邏輯的數量和復雜性Swift增加,并且還引入了客戶端和服務器之間的可能耦合。
用戶可以出售自己的物品,但他們也只能只修改自己準備出售的物品。 用戶Bob不能修改用戶Steve的項目,反之亦然。 解決此問題的常見方法可能是在項目實體內返回一個指定所有權的新字段,但是現在我們正在修改項目,只是為了使我們的客戶端可以出于任何業務原因向用戶正確呈現界面。
現在,我們正在引入客戶端和服務器之間的耦合,并且它們之間的界限很快開始變得模糊。 有了HATEOAS服務,至少對于客戶而言,這種復雜性就很多了,并且我們的項目實體保持不變。 以下是一些帶有和不帶有HATEOAS的示例請求,請注意,在HATEOAS示例中,響應的外觀與用例1的響應相同。
沒有HATEOAS:
Request: [Headers] user: jim roles: USER GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“owner” : “jim” }使用HATEOAS:
Request: [Headers] user: jim roles: USER GET: /items/1234 Response: HTTP 1.1 200 {“id” : 1234,“description” : “FooBar TV”,“image” : “fooBarTv.jpg”,“price” : 50.00,“links” : [{“rel” : “modify”,“href” : “/items/1234”},{“rel” : “delete”,“href” : “/items/1234”}] } }摘要
盡管REST的第一個樣式約束要求將客戶端和服務器之間的關注點分離,但是在不實現HATEOAS的過程中,此樣式約束受到了損害。 圍繞如何計算用戶狀態的業務邏輯進行更改意味著需要在客戶端和服務器上都進行更改。 客戶端和服務器的獨立可擴展性丟失(必須同步客戶端和服務器的發布),并且重復進行業務邏輯。 世界需要更多的HATEOAS來解決這個問題。
參考書目
- http://roy.gbiv.com/untangled/2008/rest-apis-must-be-超文本驅動
- http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-745
- https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
- http://martinfowler.com/articles/richardsonMaturityModel.html
- https://zh.wikipedia.org/wiki/No_Silver_Bullet
- http://www.crummy.com/
- http://www.crummy.com/writing/speaking/2008-QCon/act3.html
翻譯自: https://www.javacodegeeks.com/2016/03/dont-hate-hateoas.html
突然討厭做前端,討厭代碼
總結
以上是生活随笔為你收集整理的突然讨厌做前端,讨厌代码_不要讨厌HATEOAS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 公司备案注销清算组后可以变更法人吗?(公
- 下一篇: 中文字体临摹(中文字体linux)