GraphQL在查询级别获取数据会导致冗余/无用请求
我们正在实现GraphQL服务,它位于几个后端微服务的前面 例如,我们有一个GraphQL在查询级别获取数据会导致冗余/无用请求,graphql,graphql-js,apollo-server,express-graphql,Graphql,Graphql Js,Apollo Server,Express Graphql,我们正在实现GraphQL服务,它位于几个后端微服务的前面 例如,我们有一个产品,每个产品都有一个历史订单列表。我们的后端服务器提供两个RESTAPI,一个用于产品详细信息数据,另一个用于返回产品的历史订单列表 我们的客户端应用程序有两个页面:一个是产品详细信息页面,另一个是产品的历史订单列表 因此,在产品详细信息页面中,我们只能检索产品的详细信息数据,而在订单列表页面中,我们只需要列表数据 GraphQL模式如下所示: type ProductOrder { createAt: Dat
产品
,每个产品都有一个历史订单列表。我们的后端服务器提供两个RESTAPI,一个用于产品详细信息数据,另一个用于返回产品的历史订单列表
我们的客户端应用程序有两个页面:一个是产品详细信息页面,另一个是产品的历史订单列表
因此,在产品详细信息页面中,我们只能检索产品的详细信息数据,而在订单列表页面中,我们只需要列表数据
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。但是我们只需要订单列表数据,根本不需要产品数据。在这种情况下,我们认为产品详细信息请求是无用的,我们如何消除该请求
如果我们只发送一个请求来检索订单列表数据,可能会更好。A)以不同的方式构建您的模式:
第1版:不要将ProductOrder设置为产品上的字段
type Query {
product(productId: ID): Product
productOrders(productId: ID): [ProductOrder!]
}
type Product {
productId: ID
name: String
}
type Product {
productId: ID
details: ProductDetails!
orders: [ProductOrder!]!
}
type ProductDetails {
name: String
}
第2版:将详细信息作为产品中的子字段
type Query {
product(productId: ID): Product
productOrders(productId: ID): [ProductOrder!]
}
type Product {
productId: ID
name: String
}
type Product {
productId: ID
details: ProductDetails!
orders: [ProductOrder!]!
}
type ProductDetails {
name: String
}
使用解析程序:
const解析器={
查询:{
产品:({productId})=>productId,
},
产品:{
id:productId=>productId,
详细信息:productId=>someService.getProductDetail(productId),
orders:productId=>someService.getProductOrders(productId),
},
};
B) 如果未请求任何详细信息,则跳过提取
您可以使用解析器的第四个参数来检查查询的子字段。理想情况下,您可以使用一个库来实现这一点。我记得我们这样做的时候,前端只会请求对象的id
字段。如果是这样,我们可以简单地用{id}
解决
从“graphql字段列表”导入{fieldList};
常量解析程序={
查询:{
产品(u,{productId},ctx,resolveInfo){
常量字段=字段列表(resolveInfo);
if(fields.filter(f=>f!='orders'| | f!='id')。长度===0){
返回{productId};
}
返回someService.getProductDetail(productId);
},
},
};
C) 延迟提取,直到查询子字段
如果您已经在使用Dataloader,那么这相对容易做到。您不再立即在查询解析器中获取细节,而是再次传递id,让每个细节字段自己获取细节。这似乎是反直觉的,但Dataloader将确保您的服务只被查询一次:
const解析器={
查询:{
产品:({productId})=>productId,
},
产品:{
id:productId=>productId,
//所有其他详细信息字段也是如此
名称:(productId,args,ctx)=>ctx.ProductDetailsByIdLoader.load(productId)
.然后(product=>product.name),
orders:productId=>someService.getProductOrders(productId),
},
};
如果没有dataloader,可以构建一个简单的代理:
类proxy{
建造师(id){
this.id=id;
设cached=null;
this.getDetails=()=>{
如果(缓存===null){
cached=someService.getProductDetails(productId)
}
返回缓存;
}
}
//不需要参数,但您可以查看graphql js的工作方式
productId(参数、ctx、resolveInfo){
返回此.id;
}
名称(args、ctx、resolveInfo){
返回此.getDetails().then(details=>details.name);
}
订单(args、ctx、resolveInfo){
返回someService.getProductOrders(this.id);
}
}
常量解析程序={
查询:{
product:({productId})=>newproductproxy(productId),
},
//这里不需要产品解析程序
};
Thx。方法C看起来就像PayPal在文章中使用的方法。但是为每个字段显式地编写解析器在某种程度上是重复的,特别是当它涉及到一个包含太多字段的复杂类型时。方法C也是我理解用Hack编写的Facebooks GraphQL代码的方式。我认为现在他们生成了很多这样的样板,但我认为项目越大,显式代码的问题就越少?看来我也得走这条路。顺便问一下,Facebook是否公开了一些GraphQL代码的源代码?我没有找到。我不知道他们在做什么,但丹·谢弗展示了类似的东西(特别是22:15)。FB人员在会议上也发表了更多的讲话。我不会想太多,只要选择一些东西,一旦你更喜欢另一种方式,就改变它。FB会谈讨论了他们是如何一次又一次地改变做事方式的。