"There is no such thing as a new idea. It is impossible. We simply take a lot of old ideas and put them into a sort of mental kaleidoscope."

-- 马克·吐温 <马克·吐温自传>

我自己的几个工具(@jinyexin/core, @jinyexin/corecli, @jinyexin/wechat)是基于REST设计的,在定义应该由框架自动生成哪些REST接口的时候,总是感觉到有很多痛点(下文谈到REST的时候会讲)。于是GraphQL进入了我的视线。

为了比较各种API设计的优劣,我想先回顾下历史上各个时代的API设计理念,从而理解为什么我们需要“又”一种新的API设计方式。

历史上,按照时间的先后,大致出现过4种主流的设计:

  • RPC
  • SOAP
  • REST
  • GraphQL

前言:新技术的成本核算

新技术是总让人着迷的,但在给一个组织、一个团队或者一个框架引入一种新的技术或工具时,有很多东西需要衡量。比如:学习的成本、把旧的功能在将来的某个时间点转换成新技术的成本、某段时间内维护新旧两套技术的成本等。有了这么多成本的考量,新技术必须显著的“更快、更高、更强”才行。(奥利匹克格言)

GraphQL在大部分应用场景中,在我看来,是利大于弊的。


(一)RPC(远古时期,60s-90s)

RPC(Remote Procedure Call), 中文“远程方法调用”。几乎可以算是一种最最古老的API设计模式了。

60年代,计算机刚刚诞生的时候,每台计算机都有几间房间那么大。(参见下图)

少的可怜的带宽和计算能力、计算机数量使得当时的工程师优先考虑远程方法调用,而不是互相暴露的数据接口这种更通用的方法。(API驱动的开发当时还只是理论上的事情)

一直到90年代为止,以CORBACommon Object Request Broker Architecture),Java的RMI(Remote Method Invocation)为代表,绝大部分计算机都以RPC这种交互模式工作。

RPC有自己的优点。对于开发人员来说,RPC的设计理念是使远程方法调用体验、写法都和本地方法调用一样。RPC屏蔽了网络细节,虽然缓慢得多且不太可靠,但RPC使得跨系统的异步调用保持了代码连续性。

请注意,直到今天为止,在执行某些不可靠或者异步的操作时(数据库操作、外部HTTP服务调用),我们仍然为了保持代码的连续性而在奋斗(如:有了async/await,你仍然需要promise

RPC的另外一个优点是对数据结构没有任何限制,RPC的关注点是方法,只要方法需要,任何格式或者结构的数据都能传给远程方法,任何格式或者结构的数据都能被远程方法返回,非常灵活。RPC的灵活也带来了性能方面的优势,相比于固定于XML格式的SOAP,限制在HTTP上的REST、GraphQL。RPC可以任何你认为合理的方式来实现,在追求极致性能的场合下,还是有很大的用武之地的。

然而,RPC的优点也造成了RPC的缺点。

首先,保持代码的连续性需要上下文。RPC从设计上,就混淆了本地代码和远程代码,把本地系统和远程系统紧紧的耦合在一起。还记得高内聚低耦合(High cohesion & Low coupling)的设计标准么?很长一段时间,访问远程系统的主流方式是引入对方的SDK。

另外,相对于更关注于数据的接口设计方式,这种高耦合的还让开发人员很难区分本地和远程代码的边界。我需要知道这么多远程系统的代码级实现细节吗?我需要捕获发生在远程系统错误吗?怎么处理这些错误?如果远程系统没有响应怎么办?相应调用失败后如何重试?

RPC的另外一个重要缺点是API的扩散。理论上来说,一个RPC服务应该暴露一组少量且完备的API,足以且只足以让客户端完成指定任务。然而,大量的调用节点(方法),几乎没有数据结构,使得这项任何非常有挑战性。在实际的项目中,特别是长期大的项目或者开发人员变动时,很难使这些暴露给客户端的方法地稳定且保持在一个合理的数量上。

另外,RPC也缺少统一的标准。RPC的核心模块:通讯、序列化,通讯可以TCP可以HTTP,序列化和反序列化更有无数的坑。微服务名著Building Microservices里面就在一直吐槽RPC的坑和缺点。

下集预告:SOAP