翻譯 -- REST is the new SOAP

原文

https://medium.freecodecamp.org/rest-is-the-new-soap-97ff6c09896d

簡介

  • 幾年前,我在一家大型電信公司開發了一個新的信息系統。咱們不得不與愈來愈多的Web服務進行通訊,這些Web服務由較早的系統或業務合做夥伴提供。不用說,咱們共享了SOAP地獄:深奧難懂的WSDL,不兼容的庫,奇怪的錯誤等等,因此,一旦有可能,咱們就提倡並使用簡單的遠程過程調用協議:XMLRPC或JSONRPC。第一批RPC協議的服務端和客戶端是很是基礎的,有限的,脆弱的。可是逐漸地,咱們改進了它們;用了幾百行額外的代碼,咱們實現了這樣的夢想:支持不一樣的方言(如Apache特定的XMLRPC擴展),python異常與分級錯誤碼之間的內置轉換,功能和技術錯誤的隔離,自動重試,請求處理先後的日誌記錄和統計,輸入數據的全面驗證等等。如今,只需幾行代碼,咱們就能夠可靠地接入任何這樣的API。如今,僅提供一些(python語言的)裝飾器和文檔更新,咱們就可以向普遍受衆的服務器和WEB瀏覽器提供任意組合的功能,而且對咱們的系統管理員來講,不一樣的應用程序(微服務式)間連通是同一項工做,由於從軟件方面來看,服務間的連通幾乎是透明的。

  • 如今來看REST--具象主義的狀態轉換--動搖服務間通訊基礎的新浪潮。RPC已死,將來是RESTful:每一個資源都擁有本身的URL,而且只經過HTTP協議操做。今後之後,即便說--咱們提供的或消費的每個API都成爲了一個新的挑戰--也算不上瘋狂。

REST有什麼問題?

  • 例子雖小但值得細說。下面是個簡單的API,爲了可讀性刪除了數據類型。
createAccount(username, contact_email, password) -> account_id
addSubscription(account_id, subscription_type) -> subscription_id
sendActivationReminderEmail(account_id) -> null
cancelSubscription(subscription_id, reason, immediate=True) -> null
getAccountDetails(account_id) -> {full data tree}
  • 只需添加合理的異常定義(InvalidParameterError,MissingParameterError,WorkflowError...),並使用子類來表示重要的狀況(例如AlreadyExistingUsernameError),那麼你就能夠用好它。此API易於理解,易於使用,而且健壯。它不但有明確的狀態機,並且其有限的可用操做集使用戶遠離無心義的交互(如更改賬戶的建立日期)。預計將此API公開爲簡單RPC服務的時間:幾個小時。
  • 好的,如今是時候去看看RESTful的方式了。既沒有更多的標準,也沒有更精確的規範。只是一種模糊的「RESTful哲學」,傾向於無休止的形而上辯論,以及許多醜陋的解決方法。你如何將上述明確的功能映射到少數幾個CRUD操做?更新「must_send_activation_reminder_email」屬性時發送激活提醒電子郵件?或建立一個資源對應「activation_reminder_email」?若是訂閱記錄需長期存儲,而且在此期間可能重複訂閱,那麼刪除 cancelSubscription()是否明智?如何拆分getAccountDetails()返回的數據樹,以尊重REST的數據模型?
  • 你分配給每一個「資源」的URL是什麼?是的,這很容易,可是強制的。如何使用很是有限的HTTP代碼映射多種類型的錯誤?該選擇哪一種序列化格式,以支持特定的方言輸入和輸出參數?你究竟如何在HTTP方法,URL,查詢字符串,參數,標題和狀態碼之間分別使用簡潔的命名?從新發明車輪會花費你好幾個小時,並且獲得的並不是是量身定製、巧妙的輪子。一個破碎而脆弱的車輪,須要大量文件才能被理解,而且甚至不知不覺就違反了規範。

圖2

  • **REST意味着如此多的工做?**這既是一個悖論,也是一個戲虐的雙關語。讓咱們進一步深刻探討這一設計理念所產生的人爲問題。

REST動詞的喜悅

  • REST不是"CRUD",它的擁護者會確保你不會混淆這兩個。然而幾分鐘後,他們會用HTTP方法完美地定義建立(POST),檢索(GET),更新(PUT / PATCH)和刪除(DELETE)資源,並所以感到高興。他們會欣喜地自稱,這些「動詞」足以表達任何操做。那麼,它們固然是;就像英語中只用少數動詞足以表達任何概念同樣:「今天我update了個人座椅(CarDriverSeat),並create了一個點火器(EngineIgnition),但燃料箱(FuelTank)delete了它本身」。除非你是Toki Pona語言的崇拜者,不然不可能不以爲尷尬。
  • 若是重點是簡約,那麼至少讓它作對。你知道爲何PUT,PATCH和DELETE從未在Web瀏覽器表單中實現過嗎?由於它們是無用的和有害的。咱們可使用GET進行讀取和POST進行寫入。或者僅在POST時,HTTP級別的緩存不須要。其餘方法不止會妨礙你,更可能毀了你的一天。你想用PUT來更新你的資源?好的,可是一些神聖規範聲明數據輸入必須等同於GET的響應。那麼你對GET返回的衆多隻讀參數(建立時間,最後更新時間,服務器生成的令牌......)作了什麼?你忽略它們但違反PUT原則?不管如何你都會包含那些與服務器端不匹配的值,並期待一個「HTTP 409 Conflict」異常?或者你給他們隨機值,並指望服務器忽略它們(樂於隱藏錯誤)?請任意挑選。REST顯然不知道什麼是隻讀屬性,而且不會彌補這個缺陷。同時,GET請求返回在先前的POST / PUT中發送的密碼(或信用卡號碼)是很危險的; 我忘了說起PUT還會帶來危險的競爭條件,其中幾個客戶端會覆蓋彼此的變化,而它們只是想更新不一樣的領域。
  • 你想用PATCH來更新資源?很不錯,如同99%使用這個動詞的人同樣,你僅發送一個資源字段的子集,但願服務器可以正確地理解預期的操做(及其全部可能的反作用)。許多資源參數是關聯或互斥的(例如,用戶賬單信息中的信用卡卡號或PayPal令牌),但RESTful設計也隱藏了這些重要信息。不管如何,你都會違反這個規範:PATCH不支持僅發送那些須要覆蓋的部分字段。做爲替代,你更但願用一套「指令集」來操做資源。因此你再一次拿着你的紙板和咖啡杯走來走去,不得不決定如何定義這些指令。對於實際項目的規範而言,另起爐竈(Not-Invented-Here Syndrome非我發明綜合症)是REST世界事實上的標準作法。(編輯:REST的倡導者已經用Json Merge Patch迴避了這個問題,其是Json Patch的替代格式)
  • 你想刪除資源?好,但我但願你不須要提供大量的上下文數據;例如用戶的終止請求的PDF掃描件。DELETE禁止帶業務參數。REST架構師經常忽視這一限制,由於大多數Web服務器都不會對他們收到的請求執行此規則。可是如何兼容一個帶有2 MB base64字符串的DELETE請求?(編輯:RFC 2616,應該忽略沒有語義的有效載荷,如今已通過時)

  • REST愛好者輕易地聲稱「人們作錯了」,他們的API「實際上不是RESTful」。例如,許多開發人員直接使用PUT方法請求URL(/ myresourcebase / myresourceid)建立資源,然而作這件事的「好方法」(編輯:根據許多)是使用POST方法請求父URL(/myresourcebase),同時由服務器聲明該HTTP請求頭中的「Location」指示新資源的URL(編輯:儘管它不是HTTP重定向)。好消息是:這都是可有可無的。這些嚴格的原則就像高位優先挑戰低位優先(Big Endian vs. Little Endian),它們也許會佔用了哲學家數個小時,但對現實中的問題影響甚微,即「完成任務」(getting stuff done)。 順便說一句...手工製做URL請求老是頗有趣。在構建REST URL時,你知道有多少實現正確地使用標識符urlencode()嗎?並很少。那就請準備好應對惡意破壞和服務端請求僞造/跨站請求僞造(SSRF/CSRF)攻擊。

REST錯誤處理的樂趣

  • 大約每一位編碼人員都可以完成「標準案例」的工做。錯誤處理是重要要素之一,決定了你的代碼是健壯的軟件仍是堆砌起來的一大堆火柴梗。HTTP提供了一個開箱即用的錯誤代碼列表。太好了,讓咱們看看。RESTful使用「HTTP 404 Not Found」來告知訪問了一個未知資源真是見鬼了,不是嗎?不能更糟的是:nginx的配置錯誤了1個小時,從而形成你的API用戶由於這個404錯誤已經清除了數百個賬戶,想一想它們被刪除了...

  • 當用戶沒有訪問第三方服務的憑據時,使用「HTTP 401 Unauthorized」聽起來能夠接受,不是嗎?可是,若是你的Safari瀏覽器中的ajax調用收到該錯誤代碼,它可能會用很是意外的密碼提示驚嚇你的最終用戶[幾年前它就讓我嘔吐]。
  • HTTP早於「RESTful」微服務就存在,Web生態系統充滿了關於其錯誤代碼含義的假設。使用它們來傳輸應用程序錯誤就像使用牛奶瓶處理有毒廢物同樣:總有一天不可避免地會出現問題。一些標準的HTTP錯誤代碼是Webdav(Web Distributed Authoring and Versioning)定義的,或者微軟定義的,還有少數代碼的定義如此模糊,以致於他們沒有任何幫助。最後,像大多數REST用戶同樣,你可能會使用自定義的HTTP編碼,例如「HTTP 418我是茶壺」,或使用未分配的數字表示應用程序的特定例外狀況。亦或者你將無恥地返回「HTTP 400 Bad Request」表示全部的功能錯誤,而後再發明笨拙的自定義錯誤格式,包含布爾值,整數值,縮寫,以及對應的錯誤信息。或者你會徹底放棄正確的錯誤處理;再或者 你僅返回一條簡單的天然語言消息,並但願調用者是一個分析問題並採起行動的人。在與這些API內生問題的互動過程當中祝你好運。

REST概念的樂趣

  • REST造就了一個職業,吹噓必須遵照他認爲正確的服務架構概念,甚至吹噓他的概念也沒有遵循的原則。如下是一些摘錄,摘自頂級網頁。
  • REST是一種CS架構。客戶端和服務器都有不一樣的問題 -- 軟件世界裏的"新聞"。
  • REST在組件之間提供了一個統一的接口 -- 那麼,當它被強制做爲整個服務生態系統的"法語"時,就跟任何其餘協議同樣了。
  • REST是一個分層系統。單個組件不能看到其直接訪問的交互層之外 -- 這聽起來像天然而然的成果,如同精心設計的鬆散耦合架構; 真讓人吃驚。
  • REST很棒,由於它是無狀態的 -- 是的,web服務背後可能有一個巨大的數據庫,但它不記得客戶端的狀態。或者,是的,實際上它記得它的身份驗證會話,它的訪問權限......但它是無狀態的。或者更確切地說,就像任何基於HTTP的協議同樣無狀態,就像前面提到的簡單RPC同樣。

  • 藉助REST,你能夠充分利用HTTP CACHING的強大功能! -- 那麼最後的結論是:一個GET請求和它的緩存控制HTTP頭確實對web緩存很友好。話雖如此,是否是本地緩存(Memcached等)足以知足99%的Web服務?失控緩存是危險的野獸; 有多少人但願以文本形式做爲API返回值,以致於即便在更新或刪除資源好久以後,鏈路上的Varnish或Proxy仍可能會持續提供過期的內容?若是出現配置錯誤,甚至可能會「永遠」傳遞它?系統默認狀況下必須是安全的。我徹底認可一些高負載的系統但願從HTTP緩存中受益,可是,與將全部操做切換到REST及其需慎重對待的錯誤處理相比,爲繁重的只讀交互暴露幾個GET節點的花費將少得多。
  • 感謝這一切,REST具備很高的性能!-- 咱們肯定嗎?任何API設計師都知道它:在本地,咱們須要細粒度的API,可以作咱們想作的任何事情;對於遠程的API,咱們須要粗粒度,用來限制網絡往返的影響。這裏又是一個REST失敗的領域。「資源」(每一個實例在其本身的端點上)之間的數據拆分天然會致使N + 1次查詢的問題。要獲取用戶的完整數據(賬戶,訂閱,賬單信息...),您必須發出足夠多的HTTP請求;並且沒法將它們並行化,由於您事先並不知道相關資源的惟一ID。再疊加上沒法僅獲取資源對象的一部分,天然會形成討厭的性能瓶頸。
  • REST提供更好的兼容性 -- 怎麼可能?爲何許多REST Web服務在其URL層級中都有"/v2/"或"/v3/"呢?使用高級語言,只要在添加/棄用參數時遵循簡單規則,就不難實現向後兼容的API。據我所知,這方面REST並無帶來任何新意。
  • REST很簡單,你們都知道HTTP!-- 那又怎樣:每一個人都知道鵝卵石,但人們在建造房屋時很樂意擁有更好的石塊。XML是一種元語言,HTTP是一種元協議。要有一個真正的應用程序協議(如「方言」是XML),你須要指定不少東西; 若是一直充實它,最終會獲得另外一個RPC協議。
  • REST很是簡單,能夠經過CURL從任何shell中查詢!-- 實際上,每一個基於HTTP的協議均可以用CURL查詢。甚至是SOAP。發出一個GET特別簡單,固然,但當手工編寫json或xml的POST請求時要祝你好運;人們一般使用固定的文件,或者在他們最喜歡的語言的命令行界面中,更方便地直接實例化完整API客戶端。
  • 「客戶不須要任何事先的服務知識就可使用它」 -- 這是我最喜歡的引用。我發現它有不少次,以不一樣的形式出現,尤爲是潛伏在HATEOAS流行詞彙周圍; 有時和謹慎(但不夠)的「除了」短語在一塊兒。儘管如此,我不知道這些人居住在哪一個幻想世界,但在這一個中,客戶端程序並非螞蟻羣落,它不會隨機瀏覽遠程API,而後根據模式識別或黑魔法決定如何最好地處理它們。偏偏相反,當把這一個字段放入一個URL請求時,客戶端對服務器端遵照聯調過程當中達成的一致語義有強烈的指望,不然就等同於打開了地獄之門。

如何正確快速地實現REST?

  • 忘掉「正確」的部分。REST就像是一種宗教,不會有凡人永遠不會掌握其天才的程度,也不會「作對」。因此真正的問題是:若是你被迫以某種RESTful的方式提供或使用web服務,如何匆忙完成這項工做,並儘快切換到更具建設性的任務?
  • 更新:事實證實,REST實際上有不少「標準」和工業化方面的努力,儘管我歷來沒有遇到過它們(也許是由於不多有人使用它們?)。更多信息在個人後續文章( https://medium.com/@pakaldebonchamp/follow-up-to-rest-is-the-new-soap-the-origins-of-rest-21c59d243438 )中。

如何實現服務端?

  • 每一個Web框架都有本身的定義URL的方式。所以期待,在現有的你喜歡的服務器API之上,引入一些重要的依賴或分好層次的手工樣板文件,能夠做爲一組REST服務。像Django-Rest-Framework這樣的庫經過在SQL/noSQL模式之上充當以數據爲中心的包裝器來自動建立REST API。若是你恰好想經過HTTP進行「CRUD」,你正用得上。可是,若是您想提供常見的「爲我作這個」(do-this-for-me)的API,包含工做流,約束,複雜的數據影響等,你將很難使用任何REST框架來知足你的需求。請準備將每一個url和對應的HTTP方法與相應的方法調用逐個調試;在分享手工異常處理的同時,將異常轉換爲相應的錯誤代碼和參數。

如何實現客戶端整合?

  • 我猜:你沒有這方面的經驗。對於每一個API集成,您必須瀏覽冗長的文檔,並按照關於如何執行N個可能操做中的每個的詳細配方。您必須手動編寫URL,編寫序列化程序和反序列化程序,並學習如何解決API的歧義。在馴服這個野獸以前,預計會有一些反覆試驗。你知道Web服務的提供者是如何彌補這些,使其易於採用?簡單來講,他們編寫本身的官方客戶端實現--爲每種主要的語言和平臺。我最近接入過這樣的訂閱管理系統。他們爲PHP,Ruby,Python,.NET,iOS,Android,Java ...提供客戶端,併爲Go和NodeJS提供一些外部貢獻。每種客戶端都有本身的Github庫。每個都有本身的清單,包括提交、缺陷跟蹤記錄、Git Pull請求。每一個都有本身的使用示例。每一個都有本身的尷尬架構,介於ActiveRecord和RPC代理之間。真是使人震驚:與真正的,有價值的,能運行的web服務相比,開發這種怪異的包裝器須要花費多少時間?

結論

  • 幾十年來,幾乎每種編程語言都使用相同的工做流程:將輸入發送給被調用方,並將結果或錯誤做爲輸出。它運行良好,至關地好。隨着REST,它的工做流程變成一個瘋狂的工做:將蘋果映射到橙子;以及讚賞HTTP規範,以便在數分鐘後更好地違反它們。在微服務愈來愈廣泛的時代,如何實現這樣一個簡單的任務-跨網絡連接庫 -仍然如此人爲地詭異和麻煩?我不懷疑一些聰明人會提供REST閃耀的案例;他們將展現他們本身的基於REST的協議,容許在任意對象樹上發現和執行CRUD操做,這要歸功於超連接;他們會解釋REST設計如此輝煌,而對於其中的概念,我沒有看到足夠的文章和論文。我不在意那些案例。桃李不言,下自成蹊。我使用簡單的RPC,用幾個小時的編碼,就能夠工做得很是健壯,如今須要數週時間,還會不停的失敗或者失望 -- 鼓搗替代了開發。
  • 幾乎透明的遠程過程調用是99%人真正須要的,現有的協議雖然不完善,但它的工做很好。對於最基礎的WEB公共標準HTTP的偏執已經致使時間和智力的巨大浪費。
REST承諾簡單但帶來複雜性。
REST承諾穩健性但提供脆弱性。
REST承諾互操做性但提供異質性。
REST是新的SOAP。

結束語

  • 將來多是光明的。還有不少優秀的協議可用,二進制或(有或沒有語法描述的)文本格式,有些使用HTTP2的新功能...因此讓咱們繼續前進,兄弟姐妹們。咱們不能永遠留在Web服務的石器時代。
  • 編輯:許多人詢問可替代協議,本主題應該有本身的故事,但能夠看看XMLRPC和JSONRPC(簡單但很是切題)或JSONWSP(包括語法描述)或特定語言層,如Pyro或RMI什麼時候用於內部使用,或者像GraphQL和gRPC這樣的新技術用於公共API ...
相關文章
相關標籤/搜索