我们正在实现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。但是我们仅需要订单列表数据,根本不需要产品数据。在这种情况下,我们认为产品详细信息请求没有用,如何消除此请求?
如果我们仅发送一个请求以检索订单列表数据,那就更好了。
答案 0 :(得分:1)
版本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),
},
};
您可以使用解析器的第四个参数来检查查询的子字段。理想情况下,您可以使用一个库。我记得我们在前端只请求对象的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);
},
},
};
如果您已经在使用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
};