GraphQL客户端的查询策略

99 阅读9分钟

GraphQL客户端的查询策略

随着越来越多的客户依赖GraphQL来查询数据,我们看到性能和可扩展性问题的出现。查询量越来越大,速度越来越慢,新的网络推广也很有挑战性。从事订单和履行工作的网络和移动开发团队花了一些时间来探索和记录我们的方法。在移动领域,我们的目标是在可靠的网络上持续实现低于一秒的页面加载。经过两年来在功能上扩大我们的订单屏幕,现在是时候重新思考我们为实现目标而操作的基础了。我们在移动和网络客户端进行了一些实验,围绕这些痛点制定策略。这些策略在内部仍然是一个非常开放的对话,但我们想分享我们所学到的东西,鼓励更多的开发者在他们的网络和移动客户端中大规模地使用GraphQL。在这篇文章中,我将根据一个查询实例来介绍其中的一些策略,并在此基础上进行扩展。

1.设计我们的基本查询

让我们以客户加载一个产品列表为例。为了支持我们的列表屏幕,我们使用下面的查询。

使用这个查询,我们可以加载前100个产品和它们的细节(名称、价格和图片)。只要我们的产品少于100个,这可能会很有效。随着我们应用程序的增长,我们需要考虑可扩展性:

  • 我们如何为过渡到分页列表做准备?
  • 我们怎样才能推出实验和新功能?
  • 我们怎样才能使这个查询随着它的增长而变得更快?

2.加载多个产品页面

好消息,我们的products 端点在Shopify的后端侧是分页的,现在可以在我们的客户上实现这个变化了!客户端主要关心的是如何找到合适的页面大小,因为这也可能对用户体验和产品产生影响。正确的页面大小可能会从一个平台改变到另一个平台,因为我们可能会在移动客户端上同时显示更少的产品(由于空间较小)。随着查询量的增加,这对性能产生了影响。

在这一步,一个好的策略是设置性能绊脚石,也就是创建某种分数(基于加载时间)来监控我们的分页查询。在我们的查询中实现分页,如果我们选择比最初的100个产品更低的数量,就会立即减少后端和前端的负载。

我们添加两个参数来控制页面大小和索引。我们还需要知道下一页是否可以显示,因此有hasNextPage 字段。现在我们的查询已经支持无限量的产品,我们可以专注于如何推出新字段。

3.控制新字段的推出

我们的产品清单在功能上不断增长,而且我们同时运行多个项目。为了确保我们能够控制我们的ProductList 查询中的变化是如何推出的,我们使用@include@skip 标签,使我们推出的一些净新字段是可选的。它看起来像这样。

在上面的例子中,description 字段被隐藏在$featureFlag 参数的后面。它变成了可选的,你需要在解析响应时取消它的值。如果$featureFlag 的值是false ,响应将以null 的形式返回。

@include@skip 标签要求任何新的字段保持相同的命名和级别,因为重命名或删除这些字段可能会导致查询中断。解决这个问题的方法是在运行时根据特征标志值动态地建立查询。

其他的推广策略可能涉及重复查询,并根据特征标志运行特定的查询,或在推广和部署前从侧面分支工作。这些策略很可能是针对项目和平台的,并伴随着更多的权衡,如复杂性、冗余的代码和可扩展性。

@include@skip 标签的解决方案对于手头的标志是很方便的,但是对于基于远程标志的条件加载呢?让我们来看看链式查询!

4.链式查询

你时常会需要连锁多个查询,可能发生这种情况的几个场景是:

  • 你的查询依赖于一个来自另一个查询的远程标志。这使得推出功能更容易,因为你可以远程控制功能的发布。在有许多生产版本的移动客户端上,这很有用。
  • 你的查询的一部分依赖于一个远程参数。与上面的情况类似,你需要一个远程参数的值来驱动你的字段。这通常与后端限制有关。
  • 你的用户体验遇到了分页的限制。你需要在屏幕加载时加载所有的页面,并连锁查询,直到你到达最后一页。这种情况大多发生在客户身上,当前的用户体验不允许分页,并且与后端更新不同步。在这种特定情况下,如果可能的话,在用户体验层面上解决问题。

我们将本地的特征标志转化为远程标志,这就是我们的查询结果:

在上面的例子中,RemoteDescriptionFlag 查询首先被执行,我们等待其结果来启动ProductsList 查询。descriptionEnabled (别名为remoteFlag )为我们的ProductsList 查询中的@include 提供动力。这意味着我们现在要在每个页面或屏幕加载时等待两个查询完成,然后才能显示我们的产品列表。这大大降低了我们的性能。解决这种情况的方法是把远程标志查询移到这个上下文之外,可能是在整个应用层面。

链式查询的TL;DR:只有在必要时才做。

5.使用并行查询

我们的产品列表查询正随着新功能的出现而大幅增长:

我们增加了搜索过滤器、用户权限和横幅。这三个部分并没有和产品列表分页联系在一起,因为如果它们被包含在ProductsList 查询中,我们每次要求一个新的页面时都要重新查询这三个端点。这降低了性能,并提供了多余的信息。随着新功能和端点的出现,这种情况不能很好地扩展,所以这听起来是一个利用并行查询的好时机。

并行查询正是它听起来的样子:在同一时间运行多个查询。通过将查询分割成可扩展的部分,并抛开屏幕的 "核心 "查询,它给我们的客户带来了好处:

  • 更快的屏幕加载:因为我们是单独查询这些端点,所以负载反而转移到了后端那边。片段被同时解决和查询,而不是在服务器端排队。在这种情况下,服务器端的扩展也比客户端更容易。
  • 随着团队的成长,更容易做出贡献:通过每个查询有一个端点,我们减少了代码冲突的风险(例如,固定装置),并为新功能标记重叠。这也使得删除一些端点变得更加容易。
  • 更容易引入增量和部分渲染的可能性:随着查询的完成,你可以开始渲染内容,为用户创造一个更快的页面加载的假象。
  • 通过将我们的分页端点留在自己的查询中,删除了多余的查询:我们只在初始查询周期后查询产品页面。

下面是我们的并行查询的一个例子:

每当这些查询中的一个变得太大,我们就应用同样的原则,再次分割,以适应逻辑和性能。什么是过大?作为一个客户开发人员,你应该通过设置目标和绊脚石来回答这个问题。为加载时间创建某种可追踪的分数,可以帮助你决定何时将查询分成多个部分。这样一来,我们产品列表中的GraphQL增长就更加有机了(这是一个着眼于可扩展性和开发者幸福感的结果),而且不会影响性能:每个查询都可以独立增长,并减少潜在的推出与代码合并冲突的数量。

只是在使用并行查询时的一个警告,当转移服务器端的负载时,确保你设置绊脚石以避免服务器过载。咨询网站可靠性专家(SRE或在Shopify,生产工程师)和后端开发人员,他们可以帮助监测使用并行查询时服务器端的性能。

另一个与并行查询相关的挑战是将部分数据响应插入到屏幕状态的。这可能需要对现有的实现进行一些重构。这可能是一个很好的机会,可以同时支持部分渲染。

在过去的四年里,我在Shopify的订单移动空间里从事运输和缩放功能的工作。作为我们商家工作流程的核心,我有机会对他们的日常工作产生共鸣。为他们提供更好的功能意味着我们必须扩展我们的解决方案。我一直在使用这些模式来实现这一目标,而且我还在发现新的模式。我喜欢GraphQL在客户端的灵活性!我希望你能使用一些模式。我希望你能在你自己的应用程序中使用这些查询技巧。如果你这样做了,请与我们联系,我们想听听你是如何扩展你的GraphQL查询的。

关于GraphQL的其他信息