【问题标题】:GraphQL fetch data at Query level results in redundant/useless requestGraphQL 在查询级别获取数据导致冗余/无用请求
【发布时间】: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。但我们仅需要订单列表数据,根本不需要产品数据。在这种情况下,我们认为产品详细信息请求没有用,我们如何消除这个请求?

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

【问题讨论】:

    标签: graphql graphql-js apollo-server express-graphql


    【解决方案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 谈话谈论他们如何一次又一次地改变他们的做事方式。
    猜你喜欢
    • 1970-01-01
    • 2013-05-20
    • 1970-01-01
    • 2021-05-02
    • 2019-02-06
    • 2021-05-05
    • 2017-11-20
    • 2020-06-20
    • 2019-03-29
    相关资源
    最近更新 更多