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 查询导致“标量的意外枚举”