翻译 -- 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 ...

转载于:https://my.oschina.net/u/1053238/blog/1624964

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值