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

查看:117
本文介绍了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设置为产品上的字段

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天全站免登陆