GraphQL 在查询级别获取数据导致冗余/无用请求

Posted

技术标签:

【中文标题】GraphQL 在查询级别获取数据导致冗余/无用请求【英文标题】:GraphQL fetch data at Query level results in redundant/useless request 【发布时间】:2020-01-19 04:10:33 【问题描述】:

我们正在实施 GraphQL 服务,该服务位于多个后端微服务的前面。

例如,我们有一个Product,每个产品都有一个历史订单列表。我们的后端服务器提供两个 REST API,一个用于产品详细数据,另一个返回产品的历史订单列表。

我们的客户端应用有两个页面:一个是产品详情页面,另一个是产品的历史订单列表。

所以,在商品详情页,我们只能获取商品的详情数据,而在订单列表页,我们只需要列表数据。

GraphQL 架构如下:

type ProductOrder 
    createAt: Date!
    userName: String!
    count: Int

type Product 
    productId: ID
    name: String
    orders: [ProductOrder!]!

Query 
    product(productId: ID): Product

和解析器是这样的

const resolvers = 
    Query: 
        product(_,  productId)
            // fetch detail data from backend API
            return await someService.getProductDetail(productId);
        
    ,
    Product: 
        orders(product)
            // fetch order list from another API
            return await someService.getProductOrders(product.productId);
        
    
;

但是我们使用上面的代码发现了一个潜在的过度请求。

当我们从订单列表页面请求订单列表数据时,我们必须先请求产品详情API,然后才能请求订单列表API。但我们需要订单列表数据,根本不需要产品数据。在这种情况下,我们认为产品详细信息请求没有用,我们如何消除这个请求?

如果我们可以只发送一个请求来检索订单列表数据,那会更好。

【问题讨论】:

【参考方案1】:

A) 以不同的方式构建您的架构:

版本 1:不要让 ProductOrder 成为 Product 上的字段

type Query 
  product(productId: ID): Product
  productOrders(productId: ID): [ProductOrder!]


type Product 
  productId: ID
  name: String

版本 2:将详细信息作为产品中的子字段

type Product 
    productId: ID
    details: ProductDetails!
    orders: [ProductOrder!]!


type ProductDetails 
  name: String

使用解析器:

const resolvers = 
  Query: 
    product: (_,  productId ) => productId,
  ,
  Product: 
    id: productId => productId,
    details: productId => someService.getProductDetail(productId),
    orders: productId => someService.getProductOrders(productId),
  ,
;

B) 如果没有请求详细信息,则跳过获取

您可以使用解析器的第四个参数来检查查询的子字段。理想情况下,您为此使用库。我记得当我们的前端只请求对象的id 字段时,我们这样做了。如果是这样,我们可以简单地使用 id 解决。

import  fieldList  from 'graphql-fields-list';

const resolvers = 
  Query: 
    product(_,  productId , ctx, resolveInfo) 
      const fields = fieldList(resolveInfo);
      if (fields.filter(f => f !== 'orders' || f !== 'id').length === 0) 
        return  productId ;
      
      return someService.getProductDetail(productId);
    ,
  ,
;

C) 延迟获取直到子字段被查询

如果您已经在使用 Dataloader,这相对容易。您无需立即在查询解析器中获取详细信息,而是再次传递 id 并让每个详细信息字段自己获取详细信息。这似乎违反直觉,但 Dataloader 将确保您的服务只被查询一次:

const resolvers = 
  Query: 
    product: (_,  productId ) => productId,
  ,
  Product: 
    id: productId => productId,
    // same for all other details fields
    name: (productId, args, ctx) => ctx.ProductDetailsByIdLoader.load(productId)
      .then(product => product.name),
    orders: productId => someService.getProductOrders(productId),
  ,
;

如果您没有数据加载器,您可以构建一个简单的代理:

class ProductProxy 
  constructor(id) 
    this.id = id;
    let cached = null;
    this.getDetails = () => 
      if (cached === null) 
        cached = someService.getProductDetails(productId)
      
      return cached;
    
  
  // args not needed but for you to see how graphql-js works
  productId(args, ctx, resolveInfo) 
    return this.id;
  
  name(args, ctx, resolveInfo) 
    return this.getDetails().then(details => details.name);
  
  orders(args, ctx, resolveInfo) 
    return someService.getProductOrders(this.id);
  


const resolvers = 
  Query: 
    product: (_,  productId ) => new ProductProxy(productId),
  ,
  // No product resolvers need here
;

【讨论】:

谢谢。 C 方法看起来就像 PayPal 在他们的文章 graphql-resolvers-best-practices 中使用的方式。但是为每个字段显式编写解析器在某种程度上是重复的,尤其是当涉及到具有太多字段的复杂 Type 时。 是的,但我认为这个默认的解析器对于 javascript 来说是非常独特的。方法 C 也是我理解 Facebook 用 Hack 编写的 GraphQL 代码的方式。我认为现在他们生成了很多这样的样板,但我认为项目越大,显式代码的问题就越少。 那么Facebook也使用C的方法吗?看来我也必须遵循这种方式。顺便说一句,Facebook 是否开源了他们的一些 GraphQL 代码?我没找到。 好吧,我不知道他们在做什么,但 Dan Schafer 展示了类似的东西 in a talk(尤其是 22:15)。 FB人员的会议也有更多的谈话。我不会想太多,只是选择一些东西,一旦你更喜欢不同的方式就改变它。 FB 谈话谈论他们如何一次又一次地改变他们的做事方式。

以上是关于GraphQL 在查询级别获取数据导致冗余/无用请求的主要内容,如果未能解决你的问题,请参考以下文章

以LeetCode为例——如何发送GraphQL Query获取数据

使用 apolloclient 在 android 中 Graphql 查询导致“标量的意外枚举”

北亚数据恢复DELL存储服务器硬盘坏道,SMART的错误冗余级别已经超过阈值导致存储不可用的数据恢复案例

GraphQL 如何在子字段级别启用基于 ID 的查询?

GraphQL 在执行期间是不是曾经冗余地访问字段?

GraphQL 在嵌套图中获取子边的附加数据