官术网_书友最值得收藏!

2.2.4 不足與爭(zhēng)議

以下是筆者所見過的關(guān)于REST能否在實(shí)踐中真正良好應(yīng)用的部分爭(zhēng)議問題,筆者將自己的觀點(diǎn)總結(jié)如下。

1)面向資源的編程思想只適合做CRUD,面向過程、面向?qū)ο缶幊滩拍芴幚碚嬲龔?fù)雜的業(yè)務(wù)邏輯。

這是遇到最多的一個(gè)問題。HTTP的四個(gè)最基礎(chǔ)的命令POST、GET、PUT和DELETE很容易讓人直接聯(lián)想到CRUD操作,以至于在腦海中自然產(chǎn)生了直接的對(duì)應(yīng)。REST所能涵蓋的范圍當(dāng)然遠(yuǎn)不止于此,不過要說POST、GET、PUT和DELETE對(duì)應(yīng)于CRUD其實(shí)也沒什么不對(duì),只是這個(gè)CRUD必須泛化去理解。這些命令涵蓋了信息在客戶端與服務(wù)端之間流動(dòng)的幾種主要方式,所有基于網(wǎng)絡(luò)的操作邏輯,都可以對(duì)應(yīng)到信息在服務(wù)端與客戶端之間如何流動(dòng)來理解,有的場(chǎng)景比較直觀,而有的場(chǎng)景則可能比較抽象。

針對(duì)那些比較抽象的場(chǎng)景,如果真不能把HTTP方法映射為資源的所需操作,REST也并非刻板的教條,用戶是可以使用自定義方法的,按Google推薦的REST API風(fēng)格,自定義方法應(yīng)該放在資源路徑末尾,嵌入冒號(hào)加自定義動(dòng)詞的后綴。譬如,可以把刪除操作映射到標(biāo)準(zhǔn)DELETE方法上,如果還要提供一個(gè)恢復(fù)刪除的API,那它可能會(huì)被設(shè)計(jì)為:


POST /user/user_id/cart/book_id:undelete

如果你不想使用自定義方法,那就設(shè)計(jì)一個(gè)回收站的資源,在那里保留還能被恢復(fù)的商品,將恢復(fù)刪除視為對(duì)該資源某個(gè)狀態(tài)值的修改,映射到PUT或者PATCH方法上,這也是一種完全可行的設(shè)計(jì)。

最后,筆者再重復(fù)一遍,面向資源的編程思想與另外兩種主流編程思想只是抽象問題時(shí)所處的立場(chǎng)不同,只有選擇不同,沒有高下之分。

·面向過程編程時(shí),為什么要以算法和處理過程為中心,輸入數(shù)據(jù),輸出結(jié)果?當(dāng)然是為了符合計(jì)算機(jī)世界中主流的交互方式。

·面向?qū)ο缶幊虝r(shí),為什么要將數(shù)據(jù)和行為統(tǒng)一起來、封裝成對(duì)象?當(dāng)然是為了符合現(xiàn)實(shí)世界的主流的交互方式。

·面向資源編程時(shí),為什么要將數(shù)據(jù)(資源)作為抽象的主體,把行為看作統(tǒng)一的接口?當(dāng)然是為了符合網(wǎng)絡(luò)世界的主流的交互方式。

2)REST與HTTP完全綁定,不適合應(yīng)用于要求高性能傳輸?shù)膱?chǎng)景中。

筆者很大程度上贊同此觀點(diǎn),但并不認(rèn)為這是REST的缺陷,正如錘子不能當(dāng)扳手用并不是錘子的質(zhì)量有問題。面向資源編程與協(xié)議無關(guān),但是REST(特指Fielding論文中所定義的REST,而不是泛指面向資源的思想)的確依賴著HTTP協(xié)議的標(biāo)準(zhǔn)方法、狀態(tài)碼、協(xié)議頭等各個(gè)方面。HTTP并不是傳輸層協(xié)議,它是應(yīng)用層協(xié)議,如果僅將HTTP用于傳輸是不恰當(dāng)?shù)摹?duì)于需要直接控制傳輸,如二進(jìn)制細(xì)節(jié)、編碼形式、報(bào)文格式、連接方式等細(xì)節(jié)的場(chǎng)景,REST確實(shí)不合適,這些場(chǎng)景往往存在于服務(wù)集群的內(nèi)部節(jié)點(diǎn)之間,這也是之前筆者曾提及的,REST和RPC盡管應(yīng)用確有所重合,但重合范圍的大小就是見仁見智的事情。

3)REST不利于事務(wù)支持。

這個(gè)問題首先要看你怎么看待“事務(wù)(Transaction)”這個(gè)概念。如果“事務(wù)”指的是數(shù)據(jù)庫那種狹義的剛性ACID事務(wù),那除非完全不持有狀態(tài),否則分布式系統(tǒng)本身與此就是有矛盾的(CAP不可兼得),這是分布式的問題而不是REST的問題。如果“事務(wù)”是指通過服務(wù)協(xié)議或架構(gòu),在分布式服務(wù)中,獲得對(duì)多個(gè)數(shù)據(jù)同時(shí)提交的統(tǒng)一協(xié)調(diào)能力(2PC/3PC),譬如WS-AtomicTransaction、WS-Coordination這樣的功能性協(xié)議,REST是不支持的,假如你理解了這樣做的代價(jià),仍堅(jiān)持要這樣做的話,Web Service是比較好的選擇。如果“事務(wù)”只是指希望保障數(shù)據(jù)的最終一致性,說明你已經(jīng)放棄剛性事務(wù)了,這才是分布式系統(tǒng)中的正常交互方式,使用REST肯定不會(huì)有什么阻礙,更談不上“不利于”。當(dāng)然,對(duì)此REST也并沒有什么幫助,這完全取決于你的系統(tǒng)的事務(wù)設(shè)計(jì),我們?cè)诘?章中再詳細(xì)討論。

4)REST沒有傳輸可靠性支持。

是的,并沒有。在HTTP中發(fā)送一個(gè)請(qǐng)求,你通常會(huì)收到一個(gè)與之相對(duì)的響應(yīng),譬如HTTP/1.1 200 OK或者HTTP/1.1 404 Not Found等。但如果你沒有收到任何響應(yīng),那就無法確定消息是沒有發(fā)送出去,抑或是沒有從服務(wù)端返回,這其中的關(guān)鍵差別是服務(wù)端是否被觸發(fā)了某些處理?應(yīng)對(duì)傳輸可靠性最簡(jiǎn)單粗暴的做法是把消息再重發(fā)一遍。這種簡(jiǎn)單處理能夠成立的前提是服務(wù)應(yīng)具有冪等性(Idempotency),即服務(wù)被重復(fù)執(zhí)行多次的效果與執(zhí)行一次是相等的。HTTP協(xié)議要求GET、PUT和DELETE應(yīng)具有冪等性,我們把REST服務(wù)映射到這些方法時(shí),也應(yīng)當(dāng)保證冪等性。對(duì)于POST方法,曾經(jīng)有過一些專門的提案,如POE(POST Once Exactly),但并未得到IETF的認(rèn)可。對(duì)于POST的重復(fù)提交,瀏覽器會(huì)出現(xiàn)相應(yīng)警告,如Chrome中“確認(rèn)重新提交表單”的提示,對(duì)于服務(wù)端,就應(yīng)該做預(yù)校驗(yàn),如果發(fā)現(xiàn)可能重復(fù),則返回HTTP/1.1 425 Too Early。另外,Web Service中有WS-ReliableMessaging功能協(xié)議用于支持消息可靠投遞。類似的,由于REST沒有采用額外的Wire Protocol,所以除了事務(wù)、可靠傳輸這些功能以外,一定還可以在WS-*協(xié)議中找到很多REST不支持的特性。

5)REST缺乏對(duì)資源進(jìn)行“部分”和“批量”處理的能力。

這個(gè)觀點(diǎn)筆者是認(rèn)同的,這很可能是未來面向資源的思想和API設(shè)計(jì)風(fēng)格的發(fā)展方向。REST開創(chuàng)了面向資源的服務(wù)風(fēng)格,但它并不完美。以HTTP協(xié)議為基礎(chǔ)給REST帶來了極大的便捷(不需要額外協(xié)議,不需要重復(fù)解決一堆基礎(chǔ)網(wǎng)絡(luò)問題,等等),但也使HTTP本身成了束縛REST的無形牢籠。這里仍通過具體例子來解釋REST這方面的局限性。譬如你僅僅想獲得某個(gè)用戶的姓名,如果是RPC風(fēng)格,可以設(shè)計(jì)一個(gè)“getUsernameById”的服務(wù),返回一個(gè)字符串,盡管這種服務(wù)的通用性實(shí)在稱不上“設(shè)計(jì)”二字,但確實(shí)可以工作;而如果是REST風(fēng)格,你將向服務(wù)端請(qǐng)求整個(gè)用戶對(duì)象,然后丟棄掉返回結(jié)果中該用戶除用戶名外的其他屬性,這便是一種過度獲取(Overfetching)。REST的應(yīng)對(duì)手段是通過位于中間節(jié)點(diǎn)或客戶端的緩存來解決這種問題,但此缺陷的本質(zhì)是由于HTTP協(xié)議完全沒有對(duì)請(qǐng)求資源的結(jié)構(gòu)化描述能力(但有非結(jié)構(gòu)化的部分內(nèi)容獲取能力,即今天多用于斷點(diǎn)續(xù)傳的Range Header),所以返回資源的哪些內(nèi)容、以什么數(shù)據(jù)類型返回等,都不可能得到協(xié)議層面的支持,要做就只能自己在GET方法的Endpoint上設(shè)計(jì)各種參數(shù)來實(shí)現(xiàn)。另外一方面,與此相對(duì)的缺陷是對(duì)資源的批量操作的支持,有時(shí)候我們不得不為此而專門設(shè)計(jì)一些抽象的資源才能應(yīng)對(duì)。譬如你準(zhǔn)備給某個(gè)用戶的名字增加一個(gè)“VIP”前綴,提交一個(gè)PUT請(qǐng)求修改這個(gè)用戶的名稱即可,而你要給1000個(gè)用戶加VIP前綴時(shí),如果真的去調(diào)用1000次PUT,瀏覽器會(huì)回應(yīng)HTTP/1.1 429 Too Many Requests。此時(shí),你就不得不先創(chuàng)建一個(gè)任務(wù)資源(如名為“VIP-Modify-Task”),把1000個(gè)用戶的ID交給這個(gè)任務(wù),然后驅(qū)動(dòng)任務(wù)進(jìn)入執(zhí)行狀態(tài)。又譬如你去網(wǎng)店買東西,下單、凍結(jié)庫存、支付、加積分、扣減庫存這一系列步驟會(huì)涉及多個(gè)資源的變化,你可能面臨不得不創(chuàng)建一種“事務(wù)”的抽象資源,或者用某種具體的資源(譬如“結(jié)算單”)貫穿這個(gè)過程的始終,每次操作其他資源時(shí)都帶著事務(wù)或者結(jié)算單的ID。HTTP協(xié)議由于本身的無狀態(tài)性,會(huì)相對(duì)不適合(并非不能夠)處理這類業(yè)務(wù)場(chǎng)景。

目前,一種理論上較優(yōu)秀的可以解決以上這幾類問題的方案是GraphQL,它是由Facebook提出并開源的一種面向資源API的數(shù)據(jù)查詢語言,如同SQL一樣,掛了個(gè)“查詢語言”的名字,但其實(shí)CRUD都做。比起依賴HTTP無協(xié)議的REST,GraphQL可以說是另一種有協(xié)議的、更徹底地面向資源的服務(wù)方式。然而凡事都有兩面性,離開了HTTP,它又面臨幾乎所有RPC框架所遇到的那個(gè)如何推廣交互接口的問題。

主站蜘蛛池模板: 青岛市| 塔河县| 吴旗县| 莲花县| 田阳县| 扶风县| 轮台县| 灵石县| 高唐县| 临湘市| 桃江县| 金沙县| 平武县| 堆龙德庆县| 连平县| 定日县| 图木舒克市| 昌黎县| 景宁| 元氏县| 呈贡县| 什邡市| 张家港市| 岢岚县| 塘沽区| 西平县| 扶风县| 互助| 弋阳县| 陇南市| 定兴县| 黄浦区| 齐齐哈尔市| 潼南县| 隆尧县| 乐山市| 黔西县| 榆中县| 津南区| 泰来县| 汕尾市|