API开发,gRPC还是GraphQL?,同时支持最流行的编程语言。GraphQL不仅仅是API的查询语言,GraphQL在API中提供了对数据的完整易懂的描述,使得客户端可以准确的获取到自己需要的数据,没有任何的冗余,也使得API随着时间的推移很容易发展,可用于构建强大的开发人员工具。两者看似目的不同,但实际上很多开发者都面临着通信场景如何取舍的问题。本文作者带领大家从实用的角度分析一下gRPC和GraphQL的权衡!1.本文开头主要介绍gRPC和GraphQL的使用时机。一、结论:建议大家在客户端与服务端通信的场景下使用GraphQL,在服务端与服务端通信的场景下使用gRPC。另外,文末会介绍异常的处理。2.背景介绍gRPC于2016年由谷歌发布,是一种高效且对开发者友好的服务器间通信协议。GraphQL由Meta于2015年发布,是一种高效、对开发人员友好的客户端-服务器通信协议。两者都比REST有明显的优势,并且有很多共同点。我们将在本文中花费大量篇幅来比较它们的特性,然后总结每个协议的优缺点。最后,我们描述了每个协议适用于哪些特定域,以及何时出现不同协议的跨域使用。三、比较gRPC和GraphQL的功能1、接口设计gRPC和GraphQL都是接口描述语言(IDL),描述了两台计算机如何通信。它们适用于不同的编程语言,我们可以使用codegen工具生成多种语言的类型化接口。IDL抽象出传输层;GraphQL与传输无关,但通常在HTTP之上工作,而gRPC在HTTP/2之上工作。我们不需要知道传输层的细节,比如REST中的方法、路径、查询参数和字面量格式。我们只需要知道如何使用高级客户端库与单个节点进行通信即可。2.信息格式gRPC使用protocolbuffers(也称为protobufs),这是一种二进制格式,只包括值(传递的内容),而GraphQL使用JSON,它是基于文本的,除了值之外还包括字段名(值的含义)。二进制格式加上要发送的信息较少,通常会导致gRPC消息比GraphQL消息更小。(虽然在GraphQL中可以使用高效的二进制格式,但它很少被使用,并且大多数库和工具都不支持)。影响消息大小的另一个方面是过度获取:我们决定是只请求特定字段还是始终接收所有字段(通过避免“过度获取”来过滤掉我们不需要的字段,只接受我们需要的字段)。因此,对于GraphQL,可以使用overfetching技术来指定请求中需要哪些字段,而在gRPC中,我们可以使用FieldMasks作为请求的可重用过滤器,这与overfetching具有相同的效果。gRPC使用二进制格式的另一个好处是比GraphQL更快地序列化和解析消息。当然缺点也很明显,比JSON更难查看和调试,毕竟JSON的信息结构更适合人类阅读。在Temporal项目(开源微服务平台)中,我们默认使用protobuf的JSON格式,方便开发者体验的可见性。(这样就失去了二进制格式带来的效率,但是更看重效率的用户可以转用二进制格式)。3、默认值gRPC在消息中不包含默认值,而GraphQL可以为参数设置默认值,但不能为请求字段或响应类型设置默认值。这是导致gRPC消息大小更小的另一个因素。也会影响消费gRPCAPI的DX,不设置输入字段和设置该字段为默认值没有区别,默认值也是基于字段类型的值。我们不能将`behavior`枚举输入字段默认为`BEHAVIOR_FOO=2`-我们必须将默认值放在首位(`BEHAVIOR_FOO=0`),这意味着它始终是默认值,或者我们遵循推荐的做法是定义一个BEHAVIOR_UNSPECIFIED=0枚举值。enumBehavior{behavior_unspecified=0.behavior_foo=1;behavior_bar=2;}APIprovider需要传达UNSPECIFIED的意思(通过文档说明“unspecifiedwillusethedefaultbehavior,currentlyFOO”),消费者需要考虑服务器的默认行为在未来是否会改变(如果服务器在消费者正在创建一个持有UNSPECIFIED/0值的业务实体,而服务器后来更改了默认行为,则该实体将有所不同),以及是否需要进行此更改。如果不需要,客户需要将此值设置为当前默认值。这种方式比gRPC版本更简单,这样我们就可以知道如果不提供该字段会发生什么,而不需要我们自己考虑是否传递默认值。其他类型的默认值也有一些特殊情况。对于数字,有时默认值0是有效值,而其他时候则意味着不同的默认值。对于布尔值,默认值false会导致字段以负数命名。当我们在编码时命名布尔变量时,我们使用前向命名。例如,我们通常声明letretryable=true而不是letnnotallow=false。前者通常被认为更具可读性,因为后者需要额外的步骤来理解双重否定(“notRetryable为false,因此它是可重试的”)。但是如果我们有一个gRPCAPI,我们希望默认状态是可重试的,那么我们必须将这个字段命名为nonRetryable,因为可重试字段的默认值为false,就像gRPC中所有的boolean值一样。4.请求格式在gRPC中,我们一次调用一个方法。如果我们需要的数据多于一个方法返回的数据,则需要调用多个方法。如果我们需要来自第一个方法的响应数据以便我们知道接下来要调用哪个方法,那么我们将连续进行多次往返。除非我们和服务器在同一个数据中心,否则会造成很大的延迟。这个问题叫做underfetching,就是请求返回的数据信息不够,所以我们需要发送多次请求来获取数据。这是GraphQL设计可以解决的问题之一。在高延迟移动连接上,能够在一次请求中获取所需的所有数据尤为重要。在GraphQL中,我们在请求中发送一个字符串,其中包括我们要调用的所有方法(称为查询和突变)以及基于第一层结果的嵌套数据。嵌套数据可能需要从服务器到数据库的后续请求,但它们通常位于同一数据中心,并且应该具有亚毫秒级的网络延迟。GraphQL的请求灵活性降低了前后端团队之间的耦合度。前端开发人员可以向请求添加更多查询或嵌套结果字段,而不是等待后端开发人员在方法的响应中添加更多数据(以便客户端可以在单个请求中接收数据)。当有一个GraphQLAPI覆盖整个数据时,在后端进行更改时后端团队的摩擦会大大减少。GraphQL请求指定了所有需要的数据字段,这意味着客户端可以使用声明式数据获取:生活视图组件接下来需要的数据,而不是强制性的数据获取(例如调用`grpcClient.callMethod()'),GraphQL客户端库将这些组合成一个请求并提供数据,可能是在数据发生变化时。在Web开发中使用React而不是jQuery:声明您的组件应该是什么样子,并让它们在数据更改时自动更新,而不必使用jQuery操作DOM。GraphQL请求格式的另一个效果是提高了可见性:服务器可以看到每个请求的字段。您可以跟踪字段使用情况并查看客户何时停止使用已弃用的字段,以便您知道何时可以删除它们以支持应该弃用的内容。跟踪在ApolloGraphOS和Stellate等常用工具中可用。5、前向兼容性gRPC和GraphQL都具有很好的前向兼容性;也就是说,很容易以不破坏现有客户端的方式更新服务器。这对于过时的移动应用程序尤其重要,但对于加载到用户浏览器选项卡中的SPA来说也是必要的,以便在服务器更新后继续工作。在gRPC中,您可以通过对字段进行数字排序、使用新数字添加字段以及不更改现有字段的类型/数量来保持向前兼容性。在GraphQL中,您可以添加字段,使用“@deprecated”指令废除旧字段(并使它们起作用),并避免将可选参数更改为必需参数。6.gRPC和GraphQL都支持服务器到客户端端到端的数据传输:gRPC有serverstreaming,GraphQL有订阅和指令@defer、@stream和@live。gRPC的HTTP/2也支持客户端和双向流(虽然当一方是浏览器时它不能是双向的)。HTTP/2也通过多路复用提高性能。gRPC内置了网络故障重试,在GraphQL中没有直接体现,但是在相应的客户端库中支持网络故障重试,比如ApolloClient的RetryLink。gRPC不能使用大多数API代理,例如ApigeeEdge,它在HTTP头上运行,当客户端是浏览器时,我们需要使用gRPC-Web代理或Connect(虽然现代浏览器确实支持HTTP/2,但没有浏览器API允许对请求的充分控制)。GraphQL默认不使用GET缓存:许多HTTP缓存对GET请求起作用,而大多数GraphQL库默认使用POST。GraphQL有很多使用GET的选项,包括将操作放在查询参数中(当操作字符串不太长时可行)、构建时间持久化查询(通常仅用于私有API)和自动持久化查询。可以在字段级别提供缓存指令(整个响应中的最短值用于Cache-Control标头的“max-age”)。7.模式和类型GraphQL有一个模式,服务器为客户端开发者发布并用来处理请求。它定义了所有可能的查询和突变,以及所有数据类型和它们之间的关系。这种模式使得合并来自多个服务的数据变得容易。GraphQL有schemastitching(将多个GraphQLAPI组合成一个代理schemaAPI)和联合(每个下游API声明如何关联共享类型,网关通过向下游API发出请求并组合结果来自动解析请求)的概念),用于创建超图(所有数据的图,包含较小的子图/部分模式)。还有一些库将其他协议代理到GraphQL,包括gRPC。与GraphQL的模式一起出现的是一个内省系统:以标准方式查询服务器的功能以确定其功能是什么。所有GraphQL服务器库都具有内省功能,一些基于内省的高级工具,例如GraphiQL,请求使用graphql-eslint和ApolloStudio进行linting,其中包括一个IDE。gRPC有自检功能,但是没有那么普及,使用它的工具也比较少。GraphQL模式支持响应式规范化客户端缓存:由于每个(嵌套的)对象都有一个类型字段,并且该类型在不同查询之间共享,我们可以告诉客户端哪个字段是该类型的ID,客户端可以规范化存储的数据对象。这启用了客户端功能,例如查询结果或更新触发更新以查看依赖于包含相同对象的不同查询的组件。gRPC和GraphQL类型之间的区别:gRPC版本3(撰写本文时的最新版本)没有必填字段:相反,每个字段都有一个默认值。在GraphQL中,服务器可以区分值是否存在,模式可以指示参数必须存在,或者响应字段将始终存在。在gRPC中,没有标准的方法可以知道方法是否会改变状态(与GraphQL相比,gRPC将查询和改变分开)。gRPC中支持Map类型,但GraphQL中不支持:如果你有一种数据类型,例如`{[key:string]:T}`,它需要通过JSON字符串类型来实现。GraphQL虽然具有模式和灵活的查询,但在公共API的速率限制方面更为复杂(对于私有API,它允许列出持久性查询)。因为一个请求中可以包含任意数量的查询,并且查询可以任意嵌套数据,所以不能只限制客户端请求的数量。成本分析速率限制也需要对整个操作实施,例如使用graphql-cost-analysis-cost-analysis库将各个字段的成本相加并将其传递给漏斗算法。4.小结将gRPc和GraphQL的异同和优缺点以小结的形式呈现给大家,方便对比分析。1.gRPC和GraphQL的相同点使用codegen的typed接口提取网络层可以有JSONresponseserverstreaming良好的前向兼容性可以避免过度获取2.gRPC和GraphQL的优缺点(一)gRPC的优势二进制格式网络传输速度更快更快的序列化、解析和验证但是,比JSONHTTP/2更难查看和调试多路复用客户端和双向流内置重试和截止日期(2)gRPC缺点需要代理或从浏览器连接使用不可用大多数API机构没有标准的方法来知道一个方法是否会改变状态(3)GraphQL优势客户端决定它想要返回哪些数据字段。结果是:没有不可访问的数据团队解耦提高识别度并更容易合并来自多个服务的数据进一步开发自检和检测声明式数据访问反应式规范化客户端缓存(4)GraphQL缺点如果我们已经有一个gRPC服务可以暴露,因此添加GraphQL服务器需要更多的后台工作HTTPGET缓存默认不工作公共API的速率限制更复杂不支持MAP类型低效的文本传输3.如何选择?(1)服务器到服务器(Server-to-Server)在服务器到服务器的通信中,低延迟往往很重要,有时需要更多类型的流,而gRPC是明确的标准。然而,在某些情况下,我们可能会发现GraphQL的一些好处更为重要。我们正在使用GraphQL联合或模式拼接来创建所有业务数据的超图,并决定按每个服务发布GraphQL子图。我们创建了两个超图端点:一个由客户端调用的外部端点,一个由服务调用的内部端点。在这种情况下,可能不值得为服务公开gRPCAPI,因为它们都可以通过超图轻松??访问。服务的数据字段会发生变化,并使使用情况在字段级别可见,因此可以删除已弃用的字段(而不是永远维护它们)。还有一个问题是我们是否应该自己进行服务器到服务器的通信。对于数据获取(GraphQL查询),这是获得响应最快的方式,但是对于修改数据(更改),就像MartinFowler的“同步调用被认为是有害的”,这导致我们使用异步的、事件驱动的架构,在服务之间编排或协调。微服务模式在大多数情况下推荐后者,为了保持DX和开发速度,我们需要基于代码的协调器而不是基于DSL的协调器。一旦在像Temporal这样的基于代码的协调器中工作,就不会再发出网络请求——平台会可靠地处理这一切。在我看来,这就是未来。(2)客户端到服务器(Client-to-Server)在客户端-服务器通信中,延迟是非常高的。我们希望能够在一次往返中获取所有数据,能够灵活地针对不同的视图获取相应的数据,并且具有强大的缓存能力,因此GraphQL是当之无愧的赢家。然而,在某些情况下我们可以选择使用gRPC来代替。如果已经有可以使用的gRPCAPI,那么添加GraphQL服务器并不值得。JSON不太适合此数据(例如,我们正在发送大量二进制数据)。原文链接:https://stackoverflow.blog/2022/11/28/when-to-use-grpc-vs-graphql/译者介绍崔浩,社区编辑,高级架构师,拥有18年的软件开发和架构经验,10年分布式架构经验。他曾经是惠普的技术专家。乐于分享,撰写了多篇阅读量超过60万的热门技术文章。《分布式架构原理与实践》作者。
