Apollo GraphQL 订阅响应不处理嵌套查询



我有以下工作正常的GraphQL 订阅:

subscription voucherSent($estId: Int!) {
voucherSent(estId: $estId) {
id
name
usedAt
sentAt
}
}

但以下内容会发送"无法读取未定义的属性'用户'"错误

subscription voucherSent($estId: Int!) {
voucherSent(estId: $estId) {
id
name
usedAt
sentAt
owner {
id
username
}
}
}

Apollo GraphQL 订阅是否处理嵌套查询?

这是我的解析器代码:

return models.Voucher.update({
sentAt: moment().format(),
usedIn: args.sentTo,
}, { where: { id: args.id } })
.then(resp => (
models.Voucher.findOne({ where: { id: args.id } })
.then((voucher) => {
pubsub.publish(VOUCHER_SENT, { voucherSent: voucher, estId: voucher.usedIn });
return resp;
})
))

Apollo Graphql 订阅有关于订阅的非常简短的文档。我想我理解你的问题,我有完全相同的问题。基于所有的源代码阅读和测试,我想我知道一个"不太好的解决方案"。

让我先解释一下为什么你的代码不起作用。你的代码不起作用是因为用户订阅和用户做了突变不是同一个人。让我详细说明一下。 我看到了你的解析器函数,我假设解析器是某个突变解析器,在该解析器中,您执行 pubsub。但问题是,在该解析器中,您的 Web 服务器正在处理进行突变的请求。它不知道谁订阅了该频道以及他们订阅了哪些字段。所以你最好的办法是发回凭证模型的所有字段,这就是你所做的

models.Voucher.findOne({ where: { id: args.id } })

但它不适用于订阅嵌套字段的订阅者。您绝对可以在广播时修改代码

models.Voucher.include("owner").findOne({ where: { id: args.id } })
.then(voucher=>pubsub.publish(VOUCHER_SENT, { voucherSent: voucher, estId: voucher.usedIn });

这就像伪代码,但你明白了。如果您总是使用嵌套字段广播数据,那么您就没问题了。但它不是动态的。如果订阅者订阅更多嵌套字段等,您将遇到麻烦。

如果你的服务器很简单,广播静态数据就足够好了。然后你可以停在这里。下一节将详细介绍订阅的工作原理。

首先,当客户端进行查询时,您的解析器将传入 4 个参数。 对于订阅解析器,前 3 个并不重要,但最后一个包含查询、返回类型等。此参数称为 Info。 假设您进行了订阅

subscription {
voucherSent(estId: 1) {
id
name
usedAt
sentAt
}
}

另一个常规查询:

query {
getVoucher(id:1) {
id
name
usedAt
sentAt
}
}

Info 参数是相同的,因为它存储返回类型、返回字段等。 根据设置解析器的方式,如果查询包含嵌套字段,您应该有某种方法手动获取结果。

现在,有两个地方需要编写代码。 1. 订阅解析程序。在阿波罗的文档中,例如:

Subscription: {
postAdded: {
// Additional event labels can be passed to asyncIterator creation
subscribe: () => pubsub.asyncIterator([Channel Name]),
},
},

在这里,您的订阅是一个函数,其中第四个参数(信息)对于您了解用户订阅了哪些字段至关重要。所以你需要以某种方式存储它,如果你有多个用户订阅同一个凭证,但有不同的字段,存储这些是非常重要的。幸运的是,apollo graphql-subscription已经做到了这一点。

您的订阅功能应为:

Subscription{
voucherSent(estid:ID):{
subscribe: (p,a,c,Info)=>{
// Return an  asyncIterator object. 
}
}
}

要了解为什么它必须是异步迭代器对象,请查看此处的文档。所以它有一个很好的帮手,withFilter 函数,它将过滤已发布的对象。此函数将函数作为其第二个参数,该参数是决定是否应根据订阅者广播此对象的函数。这个函数,在示例中,只有 2 个参数,但在 withFilter 的源代码中,它实际上有 4 个参数,第四个是 Info,这是你需要的!

您可能还会注意到,阿波罗的订阅也有一个解析功能。这意味着,在将有效负载广播到客户端之后,您可以在该函数中修改有效负载。

Subscription{
voucherSent:{
resolve: (payload, args, context, info)=>{
// Here the info should tell you that the user also subscribed to  owner field
// Use payload.value, or id, do model.voucher.include(owner) to construct the nested fields
// return new payload. 
},
subscribe: (p,a,c,Info)=>{
// Return an  asyncIterator object. 
}
}
}

在此设置中,订阅至少应该可以工作,但可能未进行优化。因为只要有广播,服务器就会对每个订阅者进行数据库查询。每个异步迭代器调用此解析器.next。优化它的方法是,您不能依赖异步迭代器并修改每个订阅者的有效负载,您需要首先遍历所有订阅者,了解他们订阅的所有字段的联合。例如,如果用户 1

subscribe{voucherSent(id:1){id, name}}

和用户 2

subscribe{ voucherSent(id:1){name, sentAt, owner{id,name}}}

您需要将它们放在一起,并且知道您需要访问数据库一次。 假装您正在查询

getVoucher(id:1){
id
name
sentAt
owner{
id
name
}
}

然后发回此联合有效负载。这将要求您手动将所有这些订阅者存储在商店中,并在onConnect,onDisconnect中处理它们。还要弄清楚如何组合这些查询。

希望这有帮助,让我知道!

最新更新