GraphQL 在查询级别获取数据导致冗余/无用请求 [英] GraphQL fetch data at Query level results in redundant/useless request

查看:23
本文介绍了GraphQL 在查询级别获取数据导致冗余/无用请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在实现 GraphQL 服务,它位于多个后端微服务之前.

We are implementing GraphQL service, which stands in front of several backend microservices.

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

For example, we have a Product and each product has a list of history orders. Our backend server provides two REST APIs, one for product detail data, the other returns the history order list of a product.

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

Our client app has two pages: one is the product detail page, the other is the history order list of a product.

所以,在商品详情页中,我们只能检索商品的详情数据,而在订单列表页中,我们只需要列表数据.

So, in the product detail page, we can only retrieve the detail data of the product, while in the order list page, we only need the list data.

GraphQL 架构如下:

The GraphQL schema like below:

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);
        }
    }
};

但我们发现使用上述代码可能存在过度请求.

But we find a potential over-request using the above code.

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

When we request the order list data from the order list page, we have to request the product detail API first, after then we can request the order list API. But we ONLY need the order list data, no product data at all. In this case, we think the product detail request is useless, how can we eliminate this request?

如果我们只能发送一个请求来检索订单列表数据会更好.

It could be better if we can send only one request to retrieve the order list data.

推荐答案

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 } 进行解析.

B) Skip fetch if no details are requested

You can use the fourth argument to the resolver to inspect the queried subfields. Ideally you use a library for that. I remember us doing that when our frontend would only request the id field of an object. If so we could simply resolve with { 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 将确保您的服务只被查询一次:

C) Delay fetch until subfield is queried

This is relatively easy to do if you are already using Dataloader. Instead of fetching the details right away in the query resolver you again pass down the id and let each of the details fields fetch the details themselves. This seems counterintuitve but Dataloader will make sure your service is only queried once:

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),
  },
};

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

If you don't have dataloader you can build a simple proxy:

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
};

这篇关于GraphQL 在查询级别获取数据导致冗余/无用请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆