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

Thi*_* D. 1 apollo graphql react-apollo graphql-subscriptions

我有以下 GraphQL 订阅,运行良好

subscription voucherSent($estId: Int!) {
  voucherSent(estId: $estId) {
    id
    name
    usedAt
    sentAt
  }
}
Run Code Online (Sandbox Code Playgroud)

但以下发送“无法读取未定义的属性‘用户’”错误

subscription voucherSent($estId: Int!) {
  voucherSent(estId: $estId) {
    id
    name
    usedAt
    sentAt
    owner {
      id
      username
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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;
        })
    ))
Run Code Online (Sandbox Code Playgroud)

Evi*_*nta 5

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

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

 models.Voucher.findOne({ where: { id: args.id } })
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

subscription {
  voucherSent(estId: 1) {
    id
    name
    usedAt
    sentAt
  }
}
Run Code Online (Sandbox Code Playgroud)

还有另一个常规查询:

query {
  getVoucher(id:1) {
    id
    name
    usedAt
    sentAt
  }
}
Run Code Online (Sandbox Code Playgroud)

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

现在,您需要在两个地方编写代码。1. 订阅解析器。以 Apollo 的文档为例:

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

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

你的订阅函数应该是:

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

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

您可能还注意到Apollo 的订阅也有一个解析函数。这意味着,当它向客户端广播有效负载后,您可以在该函数中修改有效负载。

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. 
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在此设置中,您的订阅至少应该有效,但可能不会得到优化。因为只要有广播,服务器就可能对每个订阅者进行数据库查询。每个 asyncIterator.next 都会调用此解析器。优化它的方法是你不能依赖 asyncIterator 并修改每个订阅者的有效负载,你需要首先循环所有订阅者,知道他们订阅的所有字段的并集。例如,如果用户 1

subscribe{voucherSent(id:1){id, name}}
Run Code Online (Sandbox Code Playgroud)

和用户2

subscribe{ voucherSent(id:1){name, sentAt, owner{id,name}}}
Run Code Online (Sandbox Code Playgroud)

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

getVoucher(id:1){
  id
  name
  sentAt
  owner{
    id
    name
  }
}
Run Code Online (Sandbox Code Playgroud)

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

希望这有帮助,请告诉我!