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