2015 年 10 月 16 日 由 Dan Schafer 和 Laney Kuenzel 撰写
今年我们宣布并开源了 GraphQL 和 Relay 时,我们描述了如何使用它们通过查询执行读取操作,以及如何通过变异执行写入操作。但是,客户端通常希望在他们关心的数据发生变化时从服务器获取推送更新。为了支持这一点,我们在 GraphQL 规范中引入了第三种操作:订阅。
我们对订阅采取的方法与变异类似;就像服务器支持的变异列表描述了客户端可以执行的所有操作一样,服务器支持的订阅列表描述了它可以订阅的所有事件。就像客户端可以告诉服务器在执行变异后使用 GraphQL 选择来重新获取哪些数据一样,客户端也可以告诉服务器它希望使用 GraphQL 选择来推送哪些数据。
例如,在 Facebook 架构中,我们有一个名为 storyLike
的变异字段,客户端可以使用它来点赞帖子。客户端可能希望重新获取点赞计数,以及点赞句子(“Dan 和另外 3 人点赞”。我们在服务器上进行此转换,因为在各种语言中进行此转换的复杂性)。为此,他们将发出以下变异
mutation StoryLikeMutation($input: StoryLikeInput) { storyLike(input: $input) { story { likers { count } likeSentence { text } } }}
但是,当您查看帖子时,您还希望在其他人点赞帖子时收到推送更新!这就是订阅的用武之地;Facebook 架构有一个名为 storyLikeSubscribe
的订阅字段,允许客户端在有人点赞或取消点赞该故事时收到推送数据!客户端将创建如下订阅
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } }}
然后,客户端将此订阅发送到服务器,以及 $input
变量的值,该值将包含诸如我们要订阅的故事 ID 之类的信息
input StoryLikeSubscribeInput { storyId: string clientSubscriptionId: string}
在 Facebook,我们在构建时将此查询发送到服务器以生成一个唯一的 ID,然后订阅带有订阅 ID 的特殊 MQTT 主题,但这里可以使用许多不同的订阅机制。
在服务器端,我们会在每次有人点赞帖子时触发此订阅。如果所有客户端都使用 GraphQL,我们可以将此钩子放在 GraphQL 变异中;由于我们也有非 GraphQL 客户端,因此我们将钩子放在 GraphQL 变异之下的层级中,以确保它始终触发。
值得注意的是,这种方法要求客户端订阅它关心的事件。另一种方法是让客户端订阅查询,并在查询结果发生变化时请求更新。为什么我们没有采用这种方法呢?
让我们回顾一下我们想要为故事重新获取的数据。
fragment StoryLikeData on Story { story { likers { count } likeSentence { text } }}
哪些事件可能触发对该片段中获取的数据的更改?
这仅仅是事件的冰山一角;当有数千人订阅,数百万点赞帖子时,每个事件都会变得很棘手。为这组数据实现实时查询被证明极其复杂。
在构建基于事件的订阅时,确定什么应该触发事件的问题很容易,因为事件明确地定义了这一点。它也被证明相当容易在现有的消息队列系统之上实现。然而,对于实时查询,这似乎要困难得多。我们字段的值由其解析函数的结果决定,而弄清楚所有可能改变该函数结果的事情很困难。理论上,我们可以轮询服务器来实现这一点,但这存在效率和及时性问题。基于此,我们决定投资于基于事件的订阅方法。
我们正在积极构建上面描述的基于事件的订阅方法。我们已经使用这种方法在我们的 iOS 和 Android 应用程序上构建了实时点赞和评论功能,并且正在继续完善其功能和 API。虽然其当前在 Facebook 的实现与 Facebook 的基础设施耦合,但我们当然期待尽快开源我们的进展。
由于我们的后端和模式不支持实时查询,因此我们没有计划在 Facebook 开发它们。与此同时,很明显,有一些后端和模式适合实时查询,并且它们在这些情况下提供了很大的价值。社区对此主题的讨论非常棒,我们很高兴看到从它中涌现出什么样的实时查询提案!
订阅为创建真正动态的应用程序创造了无限的可能性。我们很高兴继续与社区合作开发 GraphQL 和 Relay,以实现这些可能性。