本文为翻译发表,转载需要注明来自公众号EAWorld。
作者:Kristopher Sandoval
译者:白小白
原题:When to Use What: REST, GraphQL, Webhooks, & gRPC
原文:https://nordicapis.com/when-to-use-what-rest-graphql-webhooks-grpc/
全文7082字,阅读约需要15分钟
看过了太多关于REST的热爱和断言,我们有时会忘记,这只诸多选择之一。REST对于相当大范畴的API来说是一个非常好的标准,但在一些需要API设计风格更细致入微的场景,还有其他的标准可供选择。
为了帮助API开发者了解使用哪种API设计风格以及在什么情况下使用,我们把REST与其他三种选择放在一起进行了一个说明,即:gRPC, GraphQL和Webhooks。我们会提供一些实际的实践案例,来分析它们的优缺点,以强调是什么核心特征使每个选项在特定场景下成为一个很好的选择。
一、REST概述
在诸多选择中,REST可能是最广为人知的,因为它在Web API中应用十分广泛。2000年,Roy Fielding在其博士论文中首次提出REST的概念。他描绘了由Web服务约束所定义的体系结构基础蓝图,在该体系结构中,应用无状态设计理念和构建Web API的标准化方法。
白小白:
Roy Thomas Fielding博士是HTTP和URI等Web架构标准的主要设计者,Apache HTTP 服务器的主要开发者。关于其博士论文的全文中文版,可以在下面这个地址得到,感兴趣可以看一哈。
https://dwz.cn/7fMFNeRr
REST本质上是无状态的,其构建方式使任何与REST兼容的Web服务都可以无状态的方式与文本化的资源表述进行交互,交互的过程采用GET、POST、PUT和其他HTTP方法作为标准化定义。
白小白:
此处的文本化的资源表述与文本化的资源是有区别的。所有格式的资源都可以进行文本化的表述,这是REST的特征。
REST的主要特性之一是它是超媒体使能的。事实上,超媒体和REST的关系是如此紧密,以至于Roy Fielding曾经声明,如果API不支持超媒体,那么从技术上讲就不是RESTful。超媒体意味着在REST API中,客户端和服务器是松耦合的,这使客户端和服务器在资源操作方面获得了极大的自由。也因此,快速迭代、服务器进化、资源弹性等元素才得以实现。
白小白:
所谓超媒体,即超文本+多媒体,这里借用了超文本的概念并在其基础上有所延伸。1963年,德特•纳尔逊(Ted Nelson)创造了术语“超文本”。1981年,德特在他的著作中使用术语“超文本”描述了这一想法:创建一个全球化的大文档,文档的各个部分分布在不同的服务器中。通过激活称为链接的超文本项目,例如研究论文里的参考书目,就可以跳转到引用的论文。
超媒体的含义其实本质上是指REST将一切网络资源进行唯一的URI定位,而不论是Word、图片或者音视频文件,并提供统一的操作方式,这种抽象的过程简化了对于资源的获取和使用,从而更加适合松耦合的微服务架构。
我的理解,所谓的超,有二层含义,一是将资源抽象为URI表达的过程,无论任何种类和格式的资源,其最终的网络存在都是一种固定规则的URI表现;二是类似超文本的含义,超文本将所有资源链接在一起形成一个大文档,而超媒体意味着你可以对不同颗粒度的REST API进行链接来达成应用目标。
超媒体的概念在后文还将解决大量与GraphQL相关的问题。
REST支持的特性远不止这些,还有分层架构,高效缓存,高可伸缩性等,使得REST成为一个高度可发现和高度可塑的解决方案,以解决许多限定场景和问题,还有不容低估的标准化HTTP表达,为终端用户提供了上下文,并使大多数交互标准化。总之,REST是现代微服务API领域非常高效、有效和强大的解决方案。
二、REST案例:PayPal
REST API的一个示例应用是PayPal REST API。PayPal强大的核心业务功能之一是为支付处理提供集成系统。因此,需要使用API简化这一过程。资源必须易于识别,调用必须是可解读的(无论有没有上下文),最重要的是,必须支持各种媒体,以便有效地处理各种各样的支付类型和方法。
白小白:
关于可解读性。REST 接口的定义强调了自描述“Self-descriptive”性。也就是上文中所讲到的调用可被解读的需求。无论是否有上下文,意味着每一次REST请求都包含关于这个请求的全部信息而不依赖其他的环境信息。这也是无状态的含义之一。下面这个图来自 https://dwz.cn/wcAR42Pu可以很好的说明这种特点。
可以看到,第二个URI是可解读的,因为包含了全部所需要的信息,即,“查询第2页的订单内容”。而第一个URI是依赖于上下文的,也不可解读,因为他的含义是“查询下一页的订单内容”,需要提供“当前是第几页”这个上下文信息才能够进行相关查询或者被理解。
为此,PayPal API的设计理念是易于理解和易于集成。以下这个示例摘自其官方文档,显示了API的一次调用如何列出一系列活动:
curl -v -X GET https://api.sandbox.paypal.com/v1/activities/activities?start_time=2012-01-01T00:00:01.000Z&end_time=2014-10-01T23:59:59.999Z&page_size=10 \-H "Content-Type: application/json" \-H "Authorization: Bearer Access-Token"
(左右滑动查看更多)
在这里,我们看到了有效的RESTful实现的印记。遵循标准的HTTP表达范式的GET方法恰如其分的完成了其检索资源的使命。在本例中,资源被明确定义为“activities”,并允许指定时区和页码的查询需求。此外,返回值是一种特定的、已知的、支持超媒体的格式。以上是一个概要性的REST介绍,也用实际的示例说明了,轻量级、无状态的系统正是将资源交付给客户端的过程中所需要的。
三、gRPC概述
REST基本上可被认为是较现代的一种设计风格,而gRPC则是对历史悠久的RPC(远程过程调用)的一种新的传承。RPC是一种在远程服务器上执行过程的方法,类似于在离您的工作站数英里的朋友的计算机上运行程序。RPC有其自身的优点和缺点,事实上,这些缺点(同时也是SOAP等系统固有的问题)正是REST开发和实现的关键。
gRPC和REST之间的一个关键区别是RPC定义其交互方式的协商机制。REST通过在HTTP请求中标准化的表达来定义交互,RPC的功能则是基于限定在客户端-服务器之间的特定协议而不是由架构本身来进行定义。RPC在很大程度上让客户端只需要执行 (同时也只在这方面负有责任),而将大部分处理和计算工作转移给承载资源的远程服务器。
因此,RPC在物联网设备和其他需要定制化通信协议的低功耗设备的解决方案中非常流行。REST经常被认为对资源要求过高,而RPC甚至可以用于极低功耗情况。
gRPC是RPC概念的进一步发展,并增加了广泛的特性。其中最重要的特性是ProtoBufs。ProtoBufs是语言中立和平台无关的协议,用于对数据进行序列化,这意味着所有通信可以高效地序列化以进行高效的交流。此外,通过Google的基于令牌的系统调用SSL/TLS协议,gRPC建立了非常有效和强大的身份验证系统。最后,gRPC是开源的,这意味着系统可以被审计、迭代以及创建代码分支等等。
白小白:
gRPC使用ProtoBufs来定义服务。ProtoBufs全称是Protocol buffers,是由Google开发的一种灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,类似于XML,但与XML相比,Protocol buffers序列化后的码流更小、速度更快、操作更简单。
四、gRPC案例:
GoogleCloud,Bugsnag
gRPC很难直接演示,这很大程度上是因为,根据其官方文档的表述,gRPC通常用于“计算的最后一英里”。换句话说,gRPC通常是用来驱动和促进异构服务和API之间的通信的终端系统。
尽管如此,文档也指出,由于其可移植性,gRPC可以在移动计算场景下使用,同时也是一个中间处理系统,用于处理来自Google Cloud Bigtable Client API,Google Cloud PubSub API,以及Google Cloud Speech API的数据。这得益于gRPC提供的标准传输机制和相对灵活的数据负载,可以最好地应用于大流量、主动、高频次通信的场景。
gRPC另一个生产案例是Bugsnag。Bugsnag工程团队发现,相比RESTful,gRPC的最初的设计过程更加流畅,当然,由于教程和最佳实践的缺乏,“开发和测试gRPC的门槛相当高”。但总的来说,延迟改进和传输成本的降低使得使用gRPC的应用对于Bugsnag来说是一个巨大的成功。
白小白:
Bugsnag,应用程序实时检测应用,是一个可以针对应用程序崩溃错误进行实时检测追踪的软件测试利器工具;帮助查找、追踪手机应用和网页应用程序中出现的错误问题。很显然,作为云服务,流量、频次都是Bugsnag需要考虑的问题。因而也更适合采用gRPC的方案。
五、GraphQL概述
GraphQL对客户端-服务器关系的解决方案是独一无二的,在某种程度上是对传统的逆转。使用GraphQL,客户端将决定他们想要哪些数据,以何种格式以及如何取得这些数据。这是对服务器向客户端发号施令的经典模式的逆转,同时,GraphQL提供了大量的扩展功能。GraphQL与REST以及RPC完全不同,REST是一种体系结构,而RPC则是由客户端和服务器定义的特定协议(并在很大程度上契约是由服务器端的资源属性定义的)。
GraphQL的一个巨大好处,是在默认情况下,它通常只发送最小请求,而REST通常发送完整请求(即默认同时发送它拥有的所有内容)。正因为如此,GraphQL在一些特定的用例中更加适用,在这些场景中,需要更明确的数据类型定义,并且倾向于使用较小的数据包来进行传输。
有人说,GraphQL的好处往往被夸大了。比如,GraphQL的“无版本”的概念,就来源于废弃旧的字段,同时用新的字段替换,这其实也是REST API在演进时所考虑的问题。但GraphQL并非是“更好的REST”或者“REST的下一步”,而是“客户端和数据之间的新型关系”的另一种选择。
白小白:
关于REST API的演进,应当了解一下REST成熟度的概念。《RESTful Web Services》的合著者Leonard Richardson提出了一个REST成熟度模型,虽然围绕这一模型,争论很多,Martin Fowler、Rest之父Roy Fielding、《RESTful Web Services Cookbook》作者Subbu Allamaraju都有不同的见解。但对于全面的理解REST与GraphQL的特点,还是有帮助的(关于成熟度模型的具体含义,可参考Mryqu的文章 https://dwz.cn/JaWs9yIH)。
在这一成熟度模型中的第4级,使用超媒体作为应用状态引擎(HATEOAS);多个URI,多个HTTP方法。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。实际上解答了很多现有文章对于REST和GraphQL的误解。
一般认为,REST的多端点特性需要进行API的组合以及多次HTTP请求才能完成GraphQL一次完成的查询。也就意味着上文中所说的,当服务器端的资源发生变更,REST必须引入多版本的概念来解决,而GraphQL则只需要在查询条件上稍作修改即可。
事实上,由于HATEOAS的存在,REST可以通过在返回的资源中引入链接的概念,就可以完成类似GraphQL一样的批量查询,包括客户端智能的根据服务端资源的反馈来确定下一步应该如何动作。即大量文章所指出的GraphQL的客户端API 可以不随服务器端的变化而变化的特征,REST API在演进到了HATEOAS的阶段时,也是支持的。
“对于不使用 HATEOAS 的 REST 服务,客户端和服务器的实现之间是紧密耦合的。客户端需要根据服务器提供的相关文档来了解所暴露的资源和对应的操作。当服务器发生了变化时,如修改了资源的 URI,客户端也需要进行相应的修改。而使用 HATEOAS 的 REST 服务中,客户端可以通过服务器提供的资源的表达来智能地发现可以执行的操作。当服务器发生了变化时,客户端并不需要做出修改,因为资源的 URI 和其他信息都是动态发现的。”(来自成富的文章 https://dwz.cn/X6VG4lCS)所以,超媒体这个概念对于REST是如此的重要,也响应了前文讲到的“Roy Fielding曾经声明,如果API不支持超媒体,那么从技术上讲就不是RESTful”这个论断。
六、GraphQL案例:GitHub
使用GraphQL的一个示例是GitHub GraphQL API。虽然最初的RESTful API很强大,并且完成了所请求的操作,但是GitHub团队逐渐发现,REST API的灵活性不够。在谈到这个问题时,Github团队表述是,API的响应“同时发送的数据太多,却并不包括消费者需要的数据,”这是导致团队采纳GraphQL的最初动因。
白小白:
关于这一点,油管有个讲述REST提供冗余信息的小电影,只有1分钟,形象生动。还是个程序媛妹子讲的,2333。 https://dwz.cn/x0vGzn8F
因此,GitHub需要一种将其内容传递给请求者的新的API,这种API不需要进行多次独立、复杂的调用,可以允许用户自定义他们的请求,来说明他们到底需要什么。最重要的是,这种新的API仍然能够处理大量REST API已经有效处理的基本请求(兼容已有的REST请求)。为此,Github增加了对GraphQL的支持,以提供上述这些关键功能。
七、Webhooks概述
GraphQL是扩展API的一种选择,gRPC是对传统方法的重新配置,Wehooks是一种完全不同的提供资源的方法,与上述的所有方法都不同。Webhook,简单来说,就是在事件发生时触发的HTTP POST请求。
这又是一种对客户机-服务器模式的逆转,在传统方法中,客户端从服务器请求数据,然后服务器提供给客户端数据(客户端是在拉数据)。在Webhook范式下,服务器更新所需提供的资源,然后自动将其作为更新发送到客户端(服务器是在推数据),客户端不是请求者,而是被动接收方。
这种控制关系的反转可以用来促进许多原本需要在远程服务器上进行更复杂的请求和不断的轮询的通信请求。通过简单地接收资源而不是直接发送请求,我们可以更新远程代码库,轻松地分配资源,甚至将其集成到现有系统中来根据API的需要来更新端点和相关数据。
八、Webhook示例:
Foursquare,SendGrid
WebHooks是一个相对简单和有效的设计理念,因此,其实现同样简单和有效。Foursquare使用Webhook的方法本质上是建立一个流程,用户在其中“检入(checks in)”,就会触发一个Webhook将更新的内容推送到其他系统和门户。通过这种方式,用户可以直接与他们正在访问的位置交互,同时通过所享用的服务的相似性来建立客人之间的社交关系。
白小白:
Foursquare是一家基于用户地理位置信息(LBS)的手机服务网站,并鼓励手机用户同他人分享自己当前所在地理位置等信息。利用Foursquare服务,手机用户可“检入”某个地点,该地点可为全球任何城市的一家饭店、好友家庭居住地或一家商店等等。一旦用户检入,Foursquare将把用户当前所在位置通知给该用户的其他好友。用户可以分享关于某地某项产品和服务的体验。如果某位用户在特定地点检入的次数最多,他将获得该地点虚拟“市长”的头衔。根据头衔可以享受商家的免费服务。听起来有点像大众点评。
当深入WebHooks的实现细节时,我们通常会看到更复杂的集成场景。例如,SendGrid使用Webhook发送事件数据更新给订阅客户,向其告知对许多统计指标的变化。SendGrid甚至实现了一种复合的Webhook方法来解析电子邮件!
白小白:
SendGrid是一个电子邮件服务平台,可以帮助市场营销人员跟踪他们的电子邮件统计数据。如果需要实时获取发送邮件的状态(如:发送成功与否,对方有没有收到,收到之后的处理-打开,删除,判定为垃圾邮件等),就需要用到SendGrid的WebHook功能来进行实时的数据通知。
九、REST、GraphQL、Webhooks
和RPC的场景比较
显而易见,这些选项中没有一个比其他选项真正“更好”,而只是某一种更适合于其独特的交互场景。我们可以将这些场景归纳如下:
REST: 一种着重于进行数据传输的依赖超媒体的无状态体系结构。REST可以将各种各样的资源绑定在一起,这些资源可能以不同的格式被请求用于不同的目的。REST本质上关心无状态的资源管理,因此也更适用于这种场景。需要快速迭代和标准化HTTP表达的系统更适合采用REST。
gRPC: 一种用于请求数据的灵活而轻量级的系统。gRPC更适用于系统需要对一定量的数据进行例行处理的情况下,发出数据请求的客户端要么是低功耗的,要么是资源苛刻型的。物联网就是一个很好的例子。
GraphQL: 一种用户可以自定义所需数据和格式的方法。GraphQL来自Facebook,其血统很好地展示了它的应用场景,即,请求者需要特定格式的数据来进行特定的使用,在这些场景中,数据格式及其之间的关系至关重要,没有任何其他解决方案拥有同等程度的数据的组合提供能力。
Webhooks: 数据更新自动完成,而不需要请求。如果API主要用于更新客户端数据的场景下,最好使用Webhooks。尽管可能这些API还具有其他功能,甚至是RESTful功能,但Webhook的主要用途应该是更新客户端,在资源新建或者更新时提供更新的、指定的数据。
在这些特定选项中进行选择,实际上是将您的业务功能与适当的方法相匹配,并确保系统在给定参数范围内及时响应。
十、结语
选择一种设计方法也许是API开发的早期最重要的决定。这种设计方法不仅是对API的构建,也影响着最终用户将如何与API所代表的资源进行交互。换句话说,这不仅仅是开发者层面的实现方法选择,而是定义了你将如何与你的消费者建立关系。
最终选择哪种解决方案取决于哪一种更适合你的特定场景。每种解决方案都有其非常具体的目的,因此,说一种解决方案比另一种好是不公平的。更准确的说法是,在执行某种核心功能方面,有些解决方案比其他解决方案更加擅长(类似许多RESTful解决方案试图模拟RPC功能的尝试就有待商榷)。
在代码库既定的情况下,只有你才能确定哪种解决方案最好。因此,做好功课,并从一开始就选择正确的方法,以获得一些真正的收益。你的代码将更加简洁,响应性更强,并且适合当前的情况。
白小白:Phil Sturgeon在apisyouwonthate.com发布了一张API风格选择决策树,正适合作为本文的补充阅读内容,遗憾的是少了Webhook的部分,好在这部分原本似乎也不是那么主流?,原文地址是:http://t.cn/E7PVRU3
当然,这一决策树并没有考量REST在第4级成熟度的HATEOAS阶段的超媒体特性对于一些问题的解决。但还是有一定的参考意义。
(点击图片可放大显示)
我将本图文字化表达如下,并附加了一些个人的理解与本文的结合:
00、开始。
01、客户端的类型:移动端、网页、分布式,转向2;其他,转向3;
(这里的其他,应该就是指一些IOT/低功耗设备的场景,正如前文所述,gRPC更适合的情况)
02、客户端是否使用共有的流程:是,转向4;不是,转向5;
(客户端使用共有的流程,意味着API可以更容易标准化因而更适合采用REST,而相反,则意味着定制化查询的需求更普遍,从而更适合合适GraphQL)
03、是否可以立即进行协调一致、原子化的部署:是,转向12;不是,转向14;
(RPC依赖服务器和客户端的自定义协议,一旦API写就,很难做出变更。即使变更也不是原子化的,因为涉及到服务端与客户端的代码定制化做出修改)
04、选择REST。
05、是否网络缓存很重要:是,转向4;不是,转向6;
06、是否服务端定义的客户端缓存很重要:是,转向4;不是,转向7;
(REST可以在很多层级更容易的实现缓存,包括网关、第三方托管以及客户端缓存,上述的选择事实上都是关于是否缓存更加重要。)
07、是否优先考虑减少响应的有效载荷:是,转向8;不是,转向4;
08、资源是否还有压缩的可能:是,转向4;不是,转向9;
09、是否需要减少交互次数或者批量获取:是,转向10;不是,转向4;
(上述选择都是关乎REST的“重”交互方式,正如前文所述,每一次响应是返回全部的数据,这就不利于频繁交互的场景,除非资源可压缩,否则更适合GraphQL的场景)
10、类型系统是必须的还是可选的:必须,转向11;可选,转向4;
(GraphQL是强数据类型的实现方式)
11、选择GraphQL。
12、更多远程系统执行的事件或者操作服务端:远程命令居多,转向13;操作服务端,转向2;
(RPC要求服务端执行大量的计算逻辑)
13、选择gRPC。
14、是否跨越有边界的上下文:是,转向2;不是,转向13;
(REST请求带有自描述性,并且资源可以不依赖上下文关系而被理解和识别)
关于作者:Kritopher是一位Web开发者和作者,写作关于安全和商业的文章。自2015年以来,他一直在为Nordic APIs撰写文章。
关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享,长按二维码关注
课程预告! 10月12日(今天)下午14:30普元信息副总裁袁义为大家分享《助力阿米巴经营,实现数字化转型——普元阿米加系统架构与实践分享》,在此公众号回复“YG+微信号”马上入群并完成报名!