REST 将会是新的 SOAP 已翻译 100%

puzzlex 投递于 2018/02/08 15:58 (共 19 段, 翻译完成于 09-25)
阅读 2279
收藏 31
5
加载中

简介

多年前,我所在的一家大型电信公司开发了一个新型信息系统。我们必须通过旧系统或是友商与越来越多的 web 服务进行通讯。

更不用说,我们合理的拥有 SOAP Hell 的份额,玄奥的 WSDL ,不相容的 library ,奇怪的 bug ...所以只要可以,我们就提倡使用简单的远程过程调用协议:XMLRPC 或 JSONRPC 。

我们为这些协议提供的首批服务器与客户端非常基础,单调,脆弱。 但渐渐的,我们改善了它们; 通过几百行额外的代码,我们让所想变成现实:支持不同的方言(例如 Apache 特定的 XMLRPC 扩展),python 异常和分层错误代码之间的内置转换,功能和技术错误的单独处理,后续的自动重审,请求之前或之后的相关日志记录和统计信息,输入数据的彻底验证......

lnovonl
翻译于 2018/09/04 12:33
1

现在,我们只需要几行代码,就能和这样的API建立可靠的连接。

我们也只需要稍微修饰一下,做一些文档更新,就可以暴露一套新的功能给广泛的受众、服务器或者web浏览器。

对于应用间的通信(微服务风格),系统管理员自己就可以完成这些工作;对于软件层面,这几乎是透明的。

用了30分钟集成RPC API后,程序员在休息。

然后,REST出现了。 
表述性状态转移(Representational State Transfer)。

一股复兴浪潮动摇了跨服务通信的根基。

RPC已死,未来是RESTful的:每个资源都有自己的URL,并通过HTTP协议进行操作。

然后,我们必须暴露或调用的API,成为了新的挑战;这简直愚蠢至极。

Rhys_Lee
翻译于 2018/09/12 18:15
1

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易于理解、易于使用,并且是健壮的。他们是有精准的状态机支持,但有限的可用操作集使得用户远离无意义的交互(例如修改账户的创建日期)。 

Tocy
翻译于 2018/09/06 18:50
0

将这个 API 暴露为一个简单的 RPC 服务,估计用时:几个小时。

现在,试试 RESTful。

没有太多的标准和规范,只有一个模糊的“RESTful哲学”,所以容易引起无休止的、形而上学的争论,还催生了许多不优雅的变通方案。

如何将上面明确的功能,映射为简单的 CRUD 操作?发送验证邮件,是更新一下"must_send_activation_reminder_email"属性,还是创建一个"activation_reminder_email resource"资源?如果在宽限期内,订阅仍然有效,或者可能恢复订阅,那用 DELETE 操作执行 cancelSubscription() 是否合理?如何在节点间拆分 getAccountDetails() 的数据树,来使它符合 REST 模型?

为每个资源分配什么 URL?是不难,但也需要实现。

如何使用非常有限的 HTTP 响应码,来表达错误场景的差别?

使用什么序列化、什么格式来描述输入输出?

HTTP 方法、URL、查询参数、负载、请求头、响应码,它们的分界线在哪?

Rhys_Lee
翻译于 2018/09/13 09:40
0

花费了很多时间重复造轮子,甚至造的并不是好轮子。一个不完整的、易碎的轮子,需要通过大量文档来理解它,甚至不知不觉就违反了规范。

为什么 REST 带来了这么多工作(Work)?
这是一个悖论,也是一个双关(译者注:REST 在英文中有休息的意思)。

让我们深入探讨一下这个设计哲学所产生的人为问题。

有趣的 REST

REST 不是 CRUD,它的拥护者们不会让你混淆这两者。然而不久,他们会为 HTTP 已经提供了 CRUD 的语义而欣喜,例如创建(POST)、获取(GET)、更新(PUT/PATCH)和删除(DELETE)。

他们乐于承认这几个动词足以表达任何操作。嗯,当然是这样;就像用几个动词足以表达英语中的任何概念一样:“Today I updated my CarDriverSeat with my body, and created an EngineIgnition, but the FuelTank deleted itself(今天我用我的身体更新了我的汽车座椅,并且创建了一次发动机点火,但是油箱删除了它自己)”;这是不是有点尴尬。除非你是道本语的崇拜者。

Rhys_Lee
翻译于 2018/09/13 10:53
0

追求极简是好事,但起码要做好。你知道为什么从来不在 Web 表单上使用 PUT、PATCH 和 DELETE 吗?因为它们百害而无一利。我们只需要用 GET 来读,用 POST 来写就好了。或者,当我们不需要 HTTP 层缓存时,只用 POST 就好了。其他方法好点的话也许会妨碍你,但最差的情况下会毁了你的一天。

想用 PUT 更新资源?可以,但是一些神圣的规范要求,数据输入必须与 GET 读取到的数据描述一致。那么,如何处理 GET 返回的大量只读参数呢(创建时间、最后更新时间、服务器生成的令牌……)?你准备忽略它们,而违反 PUT 使用原则吗?还是考虑它们,如果它们不符合服务端的值时,抛出"HTTP 409 Conflict"异常(强迫你再调用一次 GET……)?还是你给它们随机值,并祈祷服务端会忽略它们(沉浸在不会报错的喜悦中)?选个死法吧,REST 显然不知道什么是只读属性, 短期内也不会解决这件事。此外,用 GET 来返回密码信息(或信用卡号码)是危险的,之前都使用 POST 或 PUT;处理这些只写参数时,也只能祝君好运了。

我是不是忘了提 PUT 有可能会造成竞态条件,不同的客户端会覆盖其他客户端的变更,尽管它们只是想更新不同的字段。

Rhys_Lee
翻译于 2018/09/13 11:56
0

又想用 PATCH 来更新资源?很好,但是,就像 99% 的人使用这个动词一样,只需要在请求负载中传递资源字段的子集,然后希望服务器能够正确地理解操作意图(和所有副作用);许多资源参数或是紧密联系,又或是相互排斥的(例如:在用户账单信息中,要么是信用卡号,要么是 PayPal 令牌),但是 RESTful 设计原则对这些重要信息避而不谈。不管怎么说,你会再次违反原则:PATCH 不应该只发送一堆需要被更新的参数。相反,应该提供给服务端一些指示信息来应用到资源上(译者注:不应该发一堆参数让服务端来猜,需要给服务一些提示,让服务器理解你的操作意图)。又到你了,拿上你的纸板和咖啡杯,你必须决定如何来表达这些指示信息。通常需要自定义规范,因为没有标准就是 REST 世界里的事实标准。(编辑注:REST 倡导者在这个问题上有所让步,提出了 Json Merge Patch,这是一个 Json Patch 的候选方案)

想用 DELETE 删除资源?好,但我希望你,不需要提供大量的上下文数据;就像用户对 PDF 扫描的终止请求。DELETE 不允许包含有效负载。这一点,REST 架构师经常忽略掉,因为大多数 Web 服务器不会对收到的请求强制执行这个规则。如果一个 DELETE 请求携带一个 2MB 的 Base64 字符串,怎么兼容规范呢?(编辑注:RFC 2616 指明了没有语义的负载应该被忽略,但现在已经废弃了)

Rhys_Lee
翻译于 2018/09/14 09:38
0

REST 爱好者通常信奉“人们都做错了”,他们的 API“并不是真正的 RESTful”的。例如,许多开发者使用 PUT 直接在最终 URL 上创建资源(/myresourcebase/myresourceid), 然而,“正确的用法”(编辑注:根据很多人)应该是,在上一层URL(/myresourcebase)使用 POST 来创建资源,然后服务器通过 HTTP 的"Location"头来指明新资源的 URL(编辑注:不过,这不是 HTTP 重定向)。好消息是:这没关系。这些严格的准则就像是高位优先 vs 低位优先,它们让哲学家们费劲脑汁,但对现实没有什么影响,换言之,把事情做好就可以了。

顺便提一下……设计 URL 很有意思。你知道在构建 REST URL 时,有多少 urlencode() 的正确实现吗?如果没有,就等着接受 SSRF/CSRF 攻击吧。


当你忘记在 30 个 URL 中的其中一个对用户名进行 urlencode 时……

Rhys_Lee
翻译于 2018/09/14 10:09
0

REST错误处理中的乐趣

每一个编码者都可以使“名义上的用例”工作。错误处理就是这些功能里的一种,它将决定你的代码是否是健壮软件,还是一大推火柴棍。

HTTP提供了一系列开箱即用的错误码列表。好极了,让我们了解下。

使用“HTTP 404 Not Found”来通知那些不存在的资源,看起来很符合REST风格,难道不是嘛?太糟糕了:你的nginx被错误配置了一个小时,所以你的API消费者仅获得了404错误,并清除了数百个账户,他们认为这些账户已经被删除了... 

我们的客户,在我们因为错误而删除其数G字节的重要镜像之后。

Tocy
翻译于 2018/09/07 18:27
0

当用户对一个第三方服务没有访问权限时,使用"HTTP 401 Unauthorized",这听起来是可以接受的,不是吗?但是,如果你在 Safari 浏览器的 Ajax 调用中获得此错误码,它会弹出一个密码输入框,这会让你的客户很吃惊[几年前,确实是这样的,你可能有不同意见]。

HTTP 比 RESTful 历史长得多,Web 生态系统充满了关于错误码含义约定俗成的东西。用 HTTP 状态码来表示应用错误,就像是用牛奶瓶装剧毒废物一样:总有一天会遇到麻烦。

一些标准的 HTTP 错误码是 WebDAV 专用的,另一些是微软专用的,剩下的有一些定义模糊,没有太大用处。最后,和大多数 REST 用户一样,你可能开始随意使用 HTTP 状态码,比如“HTTP 418 I’m a teapot”或未分配的数字,以用来表达应用程序中的特定异常。或者,厚着脸皮用“HTTP 400 Bad Request”来表示所有功能错误,然后用布尔、整型代码、slug 和翻译信息整合成笨重的错误格式,填充到负载中。或者,完全放弃正确的错误处理;只返回一个用自然语言描述的普通消息,并希望调用者能够分析问题,并采取行动。当与自治系统的这些 API 交互时,只能祝君好运了。

Rhys_Lee
翻译于 2018/09/14 11:20
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(3)

孤独的探索号
孤独的探索号
除了文中提到的,RESTful 还有一堆开发与协作上的问题,导致前后端各种扯皮,项目进度总是延期。
https://github.com/TommyLemon/APIJSON/wiki

APIJSON 就很好的解决了这些问题。
自动将前端传的 JSON 参数转为 SQL 语句执行并返回结果,
期间自动校验权限、结构、内容,自动防 SQL 注入。

通过自动化 API,前端可以定制任何数据、任何结构!
大部分 HTTP 请求后端再也不用写接口了,更不用写文档了!
前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了!

在线解析
自动生成文档,清晰可读永远最新
自动生成请求代码,支持 Android 和 iOS
自动生成 JavaBean 文件,一键下载
自动管理与测试接口用例,一键共享
自动校验与格式化 JSON,支持高亮和收展

对于前端
不用再向后端催接口、求文档
数据和结构完全定制,要啥有啥
看请求知结果,所求即所得
可一次获取任何数据、任何结构
能去除重复数据,节省流量提高速度

对于后端
提供通用接口,大部分 API 不用再写
自动生成文档,不用再编写和维护
自动校验权限、自动管理版本、自动防 SQL 注入
开放 API 无需划分版本,始终保持兼容
支持增删改查、模糊搜索、正则匹配、远程函数等

后端接口和文档自动化,前端(客户端) 定制返回 JSON 的数据和结构!
创作不易,GitHub 右上角点 Star 支持下吧,谢谢^_^
https://github.com/TommyLemon/APIJSON
邪恶胖子
邪恶胖子
说白了,分工和系统没有细化
loki_lan
loki_lan
GET的参数长度限制性与服务类型资源的划分确实是REST的硬伤,至少现在用起来是非常别扭的,像上面特殊的场景还是用回SOAP吧
返回顶部
顶部