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

Apollo GraphQL subscription response doesn't handle nested queries

我有以下 GraphQL 订阅,工作正常

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

但是下面发送了一个"Cannot read property 'User' of undefined"错误

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 订阅有关于订阅的非常简短的文档。我想我理解你的问题并且我遇到了完全相同的问题。基于所有源代码阅读和测试,我想我对此了解 "not as good solution"。

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

 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 个无关紧要,但最后一个包含查询、return 类型等。此参数称为 Info。假设您进行了订阅

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

还有另一个常规查询:

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

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

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

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. 
    }
  }
}

为什么一定要是asyncIterator对象,看文档here. So it has a great helper, the withFilter function, which will filter the published object. This function takes a function as it's second parameter, which is the function that you decide if this object should be broadcasted based on the subscriber. This function, in the example, only had 2 parameters, but in the source code of withFilter,其实有4个参数,第四个是Info,就是你需要的!

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

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. 
    }
  }
}

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

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

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