WEB开发中,使用JSON-RPC好,还是RESTful API好

简单来说:不管哪个“好”还是不“好”,RESTful API在很多实际项目中并不实用。因此真的做了项目,你可能会发现只能用HTTP+JSON来定义接口,无法严格遵守REST风格。

为什么说不实际呢?因为这个风格太理想化了,比方说:

  • REST要求要将接口以资源的形式呈现。但实际上,很多时候都不太可能将一些业务逻辑看作资源。即使强制这么干了,也会非常非常别扭。登录就是登录,而不是“创建一个session”;播放音乐就是播放,而不是“创建一个播放状态“。

我们之所以要定义接口,本身的动机是做一个抽象,把复杂性隐藏起来,而绝对不是把内部的实现细节给暴露出去。REST却反其道而行之,要求实现应该是“资源”并且这个实现细节要暴露在接口的形式上。

但一个好的接口设计就应该是简单、直观的,能够完全隐藏内部细节的,不管底层是不是资源,资源的组合还是别的什么架构。此外,让业务逻辑与接口表现一致,对系统的长期维护和演进都有极大的好处。

  • REST只提供了增删改查的基本语义,其他的语义基本上不管。比如批量添加,批量删除,修改一个资源的一部分字段。区分“物理删除”和“标记删除”等等。复杂的查询更加不显示,对于像筛选这类的场景,REST明显就是个渣。这里要表扬一下GraphQL(但GraphQL有其他的问题,在此不展开)
  • REST建议用HTTP的status code做错误码,以便于“统一”,实际上这非常难统一。各种业务的含义五花八门,抽象层次高低不齐,根本就无法满足需要。比如一个404到底是代表这个接口找不到,还是代表一个资源找不到。400表达请求有问题,但是我想提示用户“你登录手机号输入的格式不对“,还是“你登录手机号已经被占用了“。既然201表示“created”,为啥deleted和updated没有对应的status code,只能用200或者204(no content)?错误处理是web系统里最麻烦的,最需要细心细致的地方。REST风格在这里只能添乱。
  • web请求参数可能散布在url path、querystring、body、header。服务器端处理对此完全没有什么章法。客户端和服务器端的研发之间还是要做约定。
  • 在url path上的变量会对很多其他的工作带来不良影响。

比如监控,本来url可以作为一个接口的key统计次数/延迟,结果url里出了个变量,所以自动收集nginx的access log,自动做监控项目增加就没法弄了。
再比如,想对接口做流量控制的计数,本来url可以做key,因为有变量,就得多费点事才行。

  • 现实中接口要处理的真正的问题,REST基本上也没怎么管。比如认证、授权、流控、数据缓存(http的etag还起了点作用)、超时控制、数据压缩……。
  • REST有很多好的工具可以便利的生成对应的代码和文档,也容易形成规范。但问题是REST在实际的项目中并没有解决很多问题,也在很多时候不合用,因此产生的代码和文档也就没什么用,必须经过二次加工才能真的用起来。因此可以基于REST+你的业务场景定义一个你自己的规范。

REST的本意是基于一个架构的假设(资源化),定义了一组风格,并基于这个风格形成约定、工具和支持。思路不错。但是因为他的架构假设就是有问题的,因此后续一系列东西都建立在了一个不稳固的基础之上。同时,REST并没有解决太多的实际问题。

是,的确,有些时候,用REST完成CRUD已经能完成任务了。此时,用REST没有什么不好的。但是,现实当中,真正的业务领域一般都会比资源的CRUD复杂的多。这时REST“基本上没解决太多实际问题”的缺点就会体现出来。我所见到的大多数情况,是会形成一种REST-like形式的接口,像REST却又不限于REST。

为了REST,我看到了太多的人在争执到底是POST还是PUT,到底用querystring还是body,到底用200还是201,到底一个单词应该用单数还是复数,到底一个请求参数应该放在url path的中间一段还是最后一段…… 真正要做的事情本身反而没人关心。而一旦把争论给一个“REST专家”看,他的回答八成是“其实你还是不懂REST”...

我觉得人生不能这么糟蹋,你觉得呢?

---- 附一个现实当中接口的开发的方式

你可以总是从REST开始,如果你要开发的东西能被自然而然的想成是一个资源。然后通过相关的工具自动生成一些代码,把这个原型和你的合作者讨论一下。这是我能想到的REST能做的一件很好的事情——快速实验。

然后如果你想认真的往下做,就可以彻底忘记REST这件事。开始自己定义业务接口,尽量不要在url里加变量。尽量只用GET和POST,减少一些沟通上的混乱。对于每个接口,好好定义可能发生的业务错误,并与PM一起协商怎么处理这些错误。认真的考虑认证、授权、流控等机制,当你开发的是和钱相关的业务尤其要留意。

最后,本文并不是说“绝对不要用REST”,而是:如果你在实际工作中用REST有了困惑,不知道某个情况下REST此时的最佳实践是什么时,不要追求“真正的REST会怎么做”,不要被REST限制住。

如果看过REST最初的那篇论文Architectural Styles and the Design of Network-based Software Architectures就会发现,当时想设计的目标是解决互联网级别的信息共享和互操作问题。而我们的大量开发者工作的主要目标是“为业务系统实现一个满足功能(比如登录,交易……)/非功能需求(比如认证,性能,可扩展性……)的接口“。并且设计接口时会区分“给第三方用的开放接口”、“给UI开发定制的接口“和“内部使用的接口”等。这些接口的设计目标都和REST当初制定的目标有差别。其中最接近的,是“开放接口”。因此可以看到有些开放接口用REST实现还是很不错的,比如github的接口,AWS S3的接口等。

但是其他两类接口与REST关注的点完全不一样。比如面向UI的接口的就要满足UI需要。此时资源不资源不太重要,而是尽量用少的roundtrip去返回这个界面需要的所有数据。接口是按照加载的优先级,而不是“资源”做切分。比如第一屏的显示要尽量一个接口先给出来,后续异步加载的数据可以用其他接口慢慢出。为UI提供的接口往往被划归为“大前端“的一部分。

而内部的接口,越接近DB的,越容易用表来mapping到“资源“,但是内部的接口需要考虑到数据整合的需要。比如底层的用户数据分为A、B、C三类,但这3个数据因为服务隔离不能直接在DB做join。需求要按照A的某个条件做排序分页,但要注入B和C的数据。这时就需要B和C提供batch读取和app注入的相关逻辑。此外还有复杂的查询条件,可动态改变的输出字段等要求。REST的“资源”概念在这里能帮上的忙有限。这也是GrpahQL尝试解决的问题。

再有一类问题是用接口实现分布式一致性的业务问题。比如下单+支付+扣库存+加积分问题。这时接口的形式并不重要,而能够支持实现SAGA或者TCC才是最关键的。而整个业务对外的感觉实际上是创建一个“事务”。早期一本叫做Resftul Web Services的书描述Restful接口做这个事情的方案是:

  1. 调用接口创建一个事务的资源
  2. 拿着事务资源的id,调用步骤1接口,步骤2接口……
  3. 拿着事务资源的id,调用事务的commit接口

这种形式不仅臃肿,还把怎么做这件事的内部细节完全暴露到了调用方,造成了耦合。而我们一般常见的做法就是一个接口POST /doSomething,然后接口实现方内部维护事务,维护commit,rollback等细节。有的时候还需要添加一些异步回调。

简单总结下,写接口的目标各自不同。而REST的目标是“实现互联网级别的信息共享系统”,这个目标和大部分开发者要实现的目标完全不同,这就不难解释为何照搬REST去做另一个领域的事情可能会非常别扭。

发布者:糖太宗,转载请注明出处:https://www.qztxs.com/archives/science/technology/6101

(2)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年5月12日 上午11:07
下一篇 2022年5月12日 下午12:23

相关推荐

  • 用户中心,1亿数据,架构如何设计?

    本文较长,可提前收藏。 用户中心,几乎是所有互联网公司,必备的子系统。随着数据量不断增加,吞吐量不断增大,用户中心的架构,该如何演进呢。   什么是用户中心业务? 用户中心是一个通用业务,主要提供用户注册、登录、信息查询与修改的服务。 用户中心的数据结构是怎么样的? 用户中心的核心数据结构为: User(uid, login_name, passwd, se...

    2022年5月14日
    3300
  • CentOS7下mysql5.7忘记root密码

    CentOS7下mysql5.7忘记root密码 步骤一 配置文件中添加skip-grant-tables 1 2 3 4 5 6 vi /etc/my.cnf # 在[mysqld]中添加skip-grant-tables [mysqld] skip-grant-tables datadir=/var/lib/mysql socket=/var/lib/m...

    技术 2022年6月1日
    2400
  • MySQL缓冲池(buffer pool),终于懂了!!!(收藏)

    应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库。   操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问。   MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO。   今天,和大家聊一聊InnoDB的缓冲池。...

    2022年5月10日
    3000
  • JSONP劫持

    jsonp劫持 jsonp是一个非官方的协议,全称是 JSON with Padding ,是基于 JSON 格式的为解决跨域请求资源而产生的解决方案。实现的基本原理是利用script元素的开放策略,网页可以得到从其他来源动态产生的json数据,因此可以用来实现跨域。 web程序如果通过JSONP的方式来跨域传递用户认证后的敏感信息时,攻击者完全可以在自己的...

    2022年5月27日
    3500
  • Redis的一些漏洞复现利用

    0x00 Redis REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 它通常被称为数据结构服务器,因为值(...

    2022年6月13日
    3800

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信