GraphQL 可以选择性地解析解析器中给定查询结果的字段吗?
Posted
技术标签:
【中文标题】GraphQL 可以选择性地解析解析器中给定查询结果的字段吗?【英文标题】:Can GraphQL optionally resolve a field given result of a query in resolver? 【发布时间】:2019-10-03 21:09:37 【问题描述】:我有以下 REST 端点:
/orders/id
returns
orderId,
orderItem,
customerId
/customers/id
returns
customerId,
firstName,
lastName
我受到这两个端点的限制,它们将被包装在我的 graphql 架构中。
我想要以下架构:
type Order
orderId: ID!,
orderItem: String,
customer: Customer
type Customer
customerId: ID!
firstName: String!
lastName: String!
type Query
getOrder(id: String!): Order,
getCustomer(id: String!): Customer
我想知道是否可以让 GraphQL 解析 Order 类型中的 Customer 对象?我了解您不能将查询的结果传递给另一个查询的参数。
我认为getOrder
的解析器是:
const getOrderResolver = axios.get(`/orders/$id`)
.then((ordersRes) =>
let customerId;
if(ordersRes.data.customerId !== null)
customerId = ordersRes.data.customerId
axios.get(`/customers/$customerId`)
.then((customerRes) => (
return
orderId: ordersRes.data.orderId
orderItem: ordersRes.data.orderItem
customer:
customerId: customerRes.data.customerId
firstName: customerRes.data.firstName
lastName: customerRes.data.lastName
)
else
return
orderId: ordersRes.data.orderId
orderItem: ordersRes.data.orderItem
customer: null
)
)
getCustomer
解析器
const getCustomerResolver = axios.get(`/customers/$customerId`)
.then((customerRes) => (
return
customerId: customerRes.data.customerId
firstName: customerRes.data.firstName
lastName: customerRes.data.lastName
)
似乎在我的解决方案中,无论是否在 getOrder
查询中查询,始终获取 Customer
类型都会产生额外的成本。是否可以重写我的 GraphQL 架构,使 GraphQL 仅在查询时才能解析 Customer
类型?
由于Customer
API 需要customerId
,因此我的ORDERS
REST API 仅返回CustomerId
的限制使得在getOrder
中难以解决
【问题讨论】:
您是如何构建架构的?您是直接调用 GraphQLSchema 构造函数还是使用像buildSchema
或 makeExecutableSchema
这样的实用函数?还是模式创建与您正在使用的其他库(例如 Apollo Server)相结合?
我通过 graphql-tools
使用 makeExecutableSchema
并通过 Apollo Server 提供服务。
【参考方案1】:
这里有两点要记住:
除非请求,否则 GraphQL 不会解析字段(即,除非请求中实际包含该字段,否则不会调用您的解析器)。 每个解析器都可以访问其“父”字段解析到的值。所以你的getOrder
解析器只能担心返回一个订单对象:
const resolvers =
Query:
getOrder: (parent, args, context, info) =>
const response = await axios.get(`/orders/$args.id`)
return response.data
,
...
,
...
请注意,如果 REST 端点返回与您的字段类型“形状”相同的响应,则无需在此处将响应数据映射到您的实际字段 - 只需返回 response.data
。
现在我们可以为订单类型的customer
字段添加解析器:
const resolvers =
Query: ... ,
Order:
customer: (parent, args, context, info) =>
if (!parent.customerId)
return null
const response = await axios.get('/customers/$parent.customerId')
return response.data
,
,
...
我们的父字段将是getOrder
(或任何其他具有Order
类型的字段)。 parent
的值将是您在该字段的解析器中返回的任何值(或者如果您返回了 Promise,则无论该 Promise 解析为什么)。因此我们可以使用parent.customerId
调用/customers
端点并从响应中返回数据。同样,只有在实际请求 customer
字段时才会调用此解析器。
【讨论】:
非常感谢!像魅力一样工作,现在在重构后端后为我节省了多次 API 往返! 是否可以为孩子的孩子定义解析器?例如,当customer
已经是 Order
的子解析器时,如何将另一个子解析器添加到它?
@Jim 每个字段都有一个类型。如果该类型是对象类型,您可以为该类型的任何字段定义解析器。因此,如果customer
的类型为Customer
并且Customer
是对象类型,您可以为Customer
上的任何字段定义解析器。 customer
是 Order
上的字段这一事实无关紧要。以上是关于GraphQL 可以选择性地解析解析器中给定查询结果的字段吗?的主要内容,如果未能解决你的问题,请参考以下文章
NestJS 5 GraphQL 错误查询在解析器中定义,但不在模式中