news 2026/9/23 5:33:32

Prisma 1 GraphQL 订阅(Subscriptions)完全指南:实时监听数据变更的 API 实战与底层原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Prisma 1 GraphQL 订阅(Subscriptions)完全指南:实时监听数据变更的 API 实战与底层原理

Prisma 1 GraphQL 订阅(Subscriptions)完全指南:实时监听数据变更的 API 实战与底层原理

【免费下载链接】prisma1💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]项目地址: https://gitcode.com/gh_mirrors/pr/prisma1

GraphQL 订阅(Subscriptions)是 Prisma 1 数据 API(即生成的 Prisma GraphQL API)中用于实时接收数据变更通知的核心能力。本指南以 docs/1.11/04-Reference/03-Prisma-API/05-Subscriptions.md 为骨架,结合仓库中server/servers/subscriptions模块的 Scala 源码与测试用例,系统讲解订阅触发事件、类型订阅(created/updated/deleted)、WebSocket 协议交互、节点与字段过滤、关系订阅的 workaround 以及多订阅组合等完整实战方案。读完你将能够:通过 GraphQL Playground 或原生 WebSocket 建立订阅连接,按节点、字段与变更类型精确过滤订阅事件,并理解订阅事件从数据库变更到推送给客户端之间的完整链路。

概述:什么触发了订阅

GraphQL 订阅让你在数据发生变化时实时收到通知。Prisma 的数据 API 定义了三种触发订阅的事件(events):

  • 一个新节点被创建CREATED
  • 一个已有节点被更新UPDATED
  • 一个已有节点被删除DELETED

下面是一个典型的订阅示例:每当有新的Post节点被创建时,服务器推送的 payload 会包含该PostdescriptionimageUrl字段:

subscription newPosts { post(where: { mutation_in: [CREATED] }) { mutation node { description imageUrl } } }

订阅使用一个专用的 WebSocket 端点(websocket endpoint)进行管理,而不是普通的 HTTP 查询端点。

可用的订阅清单如下(可通过服务内的 GraphQL Playground 探索):

  • 对于数据模型中的每一个对象类型(object type),都会自动生成一个类型订阅(type subscription),用于监听该类型上的数据变更;
  • 目前,在关系(relation)中连接或断开节点不会触发订阅!后文会介绍一种基于UPDATED订阅的 workaround(见“关系订阅”一节)。

你可以在一个订阅请求中组合多个订阅触发器,精确控制希望收到通知的事件;订阅 API 同样复用了查询(Queries)中提供的强大过滤系统。

订阅请求:如何发送订阅

订阅请求可以通过以下方式发送:

  • Apollo Client:使用apollo-link-ws库可以方便地发起订阅(这是 GraphQL 生态中最常用的做法,社区中有大量基于 React 的实时聊天类示例项目可以参照);
  • GraphQL Playground:Playground 内建对订阅的支持,可直接探索和运行订阅;
  • 任意 WebSocket 客户端:按下面的“原生 WebSockets”一节手动实现。

使用 Playground

GraphQL Playground 可以用来探索和运行 GraphQL 订阅。它会在内部自动完成 WebSocket 握手、subscription_start发送与subscription_data接收的流程,是调试订阅最快捷的方式。

使用原生 WebSockets(五步流程)

订阅通过 WebSocket 管理。完整流程分为以下五步,每一步都对应着订阅协议(graphql-subscriptions)中一个特定的消息类型。仓库源码server/servers/subscriptions/src/main/scala/com/prisma/subscriptions/protocol/SubscriptionProtocol.scalaSubscriptionProtocolV05定义了这套协议的完整消息集,其协议名即为graphql-subscriptions

1. 建立连接

首先建立 WebSocket 连接,并指定graphql-subscriptions协议:

let webSocket = new WebSocket('wss://__CLUSTER__.prisma.sh/__WORKSPACE__/__SERVICE__/__STAGE__', 'graphql-subscriptions');

其中 URL 中的__CLUSTER____WORKSPACE____SERVICE____STAGE__需要替换为你的实际集群地址、工作空间、服务名与阶段名。

2. 发起握手

监听open事件,然后向服务器发送一条typeinit的 JSON 消息完成握手:

webSocket.onopen = (event) => { const message = { type: 'init' } webSocket.send(JSON.stringify(message)) }

在源码协议定义中,这对应客户端 → 服务器的INIT消息(val INIT = "init" // Client -> Server)。

3. 接收消息

服务器会以不同的type属性返回多种消息,你可以针对每种消息做出相应处理:

webSocket.onmessage = (event) => { const data = JSON.parse(event.data) switch (data.type) { case 'init_success': { console.log('init_success, the handshake is complete') break } case 'init_fail': { throw { message: 'init_fail returned from WebSocket server', data } } case 'subscription_data': { console.log('subscription data has been received', data) break } case 'subscription_success': { console.log('subscription_success') break } case 'subscription_fail': { throw { message: 'subscription_fail returned from WebSocket server', data } } } }

这些消息类型与源码中SubscriptionProtocolV05.MessageTypes的服务器 → 客户端消息一一对应:init_success(握手成功)、init_fail(握手失败)、subscription_success(订阅建立成功)、subscription_fail(订阅建立失败)、subscription_data(订阅数据推送)。此外协议还定义了keepalive心跳消息,用于保持连接活性。

4. 订阅数据变更

发送typesubscription_start的消息来订阅数据变更:

const message = { id: '1', type: 'subscription_start', query: ` subscription newPosts { post(filter: { mutation_in: [CREATED] }) { mutation node { description imageUrl } } } ` } webSocket.send(JSON.stringify(message))

之后你会收到一条subscription_success消息;当数据发生变化时,则会收到subscription_data消息。subscription_start消息中携带的id属性会出现在所有subscription_data消息中,因此你可以在一条 WebSocket 连接上多路复用(multiplex)多个订阅,通过id区分不同订阅的数据。源码中SubscriptionsManagerForModel.Requests.StartSubscriptionid: StringOrInt唯一标识一个订阅,正是对这种多路复用能力的支撑。

5. 取消订阅

发送typesubscription_end的消息即可取消订阅:

const message = { id: '1', type: 'subscription_end' } webSocket.send(JSON.stringify(message))

在源码中,这对应SUBSCRIPTION_END消息,SubscriptionsManagerForModel收到EndSubscription请求后会把对应id的订阅从订阅集合中移除。

补充:仓库同时实现了graphql-ws协议(SubscriptionProtocolV07,即 Apollosubscriptions-transport-ws现代版本所用的connection_init/start/stop/data/complete消息体系)。使用新版 Apollo 生态时,订阅请求与响应将遵循这套消息语义,其连接初始化、开始订阅、停止订阅的流程与本节的五步流程在概念上一一对应。

类型订阅:监听某个对象类型上的数据变更

对于数据模型中每一个对象类型,Prisma 会自动生成对应的类型订阅。以如下仅包含Post类型的数据模型为例:

type Post { id: ID! @unique title: String! description: String }

在生成的 Prisma API 中,将出现一个post订阅,用于在Post类型的节点被创建、更新或删除时通知你。

订阅新创建的节点

订阅所有被创建的节点

使用where对象并设置mutation_in: [CREATED]

subscription { post(where: { mutation_in: [CREATED] }) { mutation node { description imageUrl author { id } } } }

payload 包含:

  • mutation:此处返回CREATED
  • node:允许你查询被创建节点的信息(以及其关联节点的信息)。
订阅特定的被创建节点

利用与查询相同的过滤系统(filter system),通过where对象中的node参数进一步过滤。例如,仅当某位特定用户关注(follows)了author时才通知你Post被创建:

subscription { post(where: { AND: [{ mutation_in: [CREATED] }, { node: { author: { followedBy_some: { id: "cj03x3nacox6m0119755kmcm3" } } }] }) { mutation node { description imageUrl author { id } } } }

订阅被删除的节点

订阅所有被删除的节点

设置mutation_in: [DELETED]

subscription deletePost { post(where: { mutation_in: [DELETED] }) { mutation previousValues { id } } }

payload 包含:

  • mutation:此处返回DELETED
  • previousValues:节点删除前的标量值(scalar values)。

注意:对于CREATED订阅,previousValues始终为null

订阅特定的被删除节点

同样可以使用node参数做精确过滤。例如,仅当特定用户关注了author时才通知Post被删除:

subscription { post(where: { mutation_in: [DELETED] node: { author: { followedBy_some: { id: "cj03x3nacox6m0119755kmcm3" } } } }) { mutation previousValues { id } } }

订阅被更新的节点

订阅所有被更新的节点

设置mutation_in: [UPDATED]

subscription { post(where: { mutation_in: [UPDATED] }) { mutation node { description imageUrl author { id } } updatedFields previousValues { description imageUrl } } }

payload 包含:

  • mutation:此处返回UPDATED
  • node:允许你查询被更新节点及其关联节点的信息;
  • updatedFields:发生变更的字段列表;
  • previousValues:节点更新前的标量值。

注意:对于CREATEDDELETED订阅,updatedFields始终为null;对于CREATED订阅,previousValues始终为null

订阅特定字段的更新

通过updatedFields_contains等过滤条件监听特定字段的更新。例如,仅当Postdescription字段被修改时才通知你:

subscription { post(where: { mutation_in: [UPDATED] updatedFields_contains: "description" }) { mutation node { description } updatedFields previousValues { description } } }

updatedFields_contains类似的过滤条件还有:

  • updatedFields_contains_every: [String!]:当所有指定的字段都被更新时匹配;
  • updatedFields_contains_some: [String!]:当部分指定字段被更新时匹配。

注意updatedFields系列过滤条件不能mutation_in: [CREATED]mutation_in: [DELETED]同时使用!

从源码实现看,previousValuesupdatedFields在事件处理时有着明确的区分:server/servers/subscriptions/src/main/scala/com/prisma/subscriptions/resolving/SubscriptionResolver.scala中,handleDatabaseCreateEventpreviousValues = None, updatedFields = None执行查询;handleDatabaseUpdateEvent会带上previousValueschangedFieldshandleDatabaseDeleteEvent则只带previousValuesupdatedFields = None)。这从底层印证了文档中的三条注意点。

关系订阅:监听关系变更的 workaround

目前,关系更新的订阅只能通过UPDATED订阅配合 workaround 实现

订阅关系变化

你可以通过触碰(touching)节点来强制触发变更通知:先在对应类型上添加一个dummy: String字段,然后在关系状态发生变化的节点上更新该字段:

mutation updatePost { updatePost( where: { id: "some-id" } data: { dummy: "dummy" # do a dummy change to trigger update subscription } ) }

其原理是:关系本身的连接/断开不产生独立事件,但关系变化通常会伴随对该节点的一次写操作;通过更新一个无关紧要的dummy字段,就能让节点产生一次UPDATED事件,从而借道UPDATED订阅间接感知关系变化。若希望订阅直接支持关系触发器,可以关注该特性在社区中的讨论进展。

组合订阅:一次订阅多个变更类型

你可以在同一个订阅中订阅同一类型上的多种变更。

订阅所有节点上的所有变更

利用where对象的mutation_in参数选择要订阅的变更类型。例如同时订阅createPostupdatePostdeletePost对应的三种事件:

subscription { post(where: { mutation_in: [CREATED, UPDATED, DELETED] }) { mutation node { id description } updatedFields previousValues { description imageUrl } } }

订阅特定节点上的所有变更

使用where对象的node参数圈定需要通知的特定节点,并与mutation_in组合。例如,仅当特定用户关注了作者时,才通知你该作者相关Post的创建、更新与删除:

subscription { post( where: { mutation_in: [CREATED, UPDATED, DELETED] } node: { author: { followedBy_some: { id: "cj03x3nacox6m0119755kmcm3" } } } ) { mutation node { id description } updatedFields previousValues { description imageUrl } } }

注意previousValuesCREATED订阅始终为nullupdatedFieldsCREATEDDELETED订阅始终为null

高级订阅过滤

你可以利用与查询相同的过滤系统,通过where参数实现更复杂的组合。例如,订阅所有CREATEDDELETED事件,外加imageUrl字段被更新时的所有UPDATED事件:

subscription { post(where: { OR: [{ mutation_in: [CREATED, DELETED] }, { mutation_in: [UPDATED] updatedFields_contains: "imageUrl" }] }) { mutation node { id description } updatedFields previousValues { description imageUrl } } }

注意:在任何updatedFields过滤条件与CREATEDDELETED订阅同时出现时,会返回错误。此外previousValuesCREATED始终为nullupdatedFieldsCREATEDDELETED始终为null

订阅的底层实现:从数据库事件到 WebSocket 推送

理解了订阅 API 的用法后,再来看仓库中server/servers/subscriptions模块的实现,可以更深入地理解订阅事件的处理链路。

订阅管理器:按模型组织订阅

server/servers/subscriptions/src/main/scala/com/prisma/subscriptions/resolving/SubscriptionsManagerForModel.scala每一个模型维护一个独立的订阅管理器 Actor:

  • 启动时(preStart),它会为模型的创建、更新、删除三条通道分别建立 pub/sub 订阅,并对连接断开(Terminated)与 Schema 失效(SchemaInvalidated)做出处理;
  • 收到StartSubscription时登记订阅,收到EndSubscription时移除订阅;
  • 对同一条数据库事件,它会按“查询文本 + 变量”对订阅进行分组,同一组只执行一次过滤查询,再把结果广播给组内所有订阅者,从而显著降低数据库与计算开销(runInChunksOf(maxParallelism = 10)控制并行度);
  • 当最后一个订阅者断开时,管理器会自动停止自身 Actor(context.stop(self))。

事件通道:消息总线如何路由

server/servers/subscriptions/src/main/scala/com/prisma/subscriptions/resolving/MutationChannelUtil.scala定义了事件通道的命名规则:每个模型对应三条通道subscription:event:{projectId}:create{Model}subscription:event:{projectId}:update{Model}subscription:event:{projectId}:delete{Model}。服务器(servers/apiservers/deploy等模块)在数据变更时向对应通道发布事件消息,订阅服务通过消息总线(message-bus)消费这些消息并分发给对应模型的订阅管理器,管理器再根据通道名解析出变更类型(Created/Updated/Deleted)——这正是mutation_in语义的底层来源。

事件解析:35ms 延迟缓冲与 payload 组装

SubscriptionResolver.scala负责把数据库事件解析为订阅响应:

  • 它根据mutationType将事件 JSON 解析为DatabaseCreateEventDatabaseUpdateEventDatabaseDeleteEvent三种类型;
  • 出于读写分离一致性的考虑,生产环境从可能落后主库最多约 20ms 的从库读取数据,因此代码中人为加入了35ms 的缓冲延迟(源码注释明确提醒不要移除该延迟);
  • 创建事件只携带nodeId查询当前节点;更新事件携带previousValueschangedFields(即updatedFieldspreviousValues的 payload 来源);删除事件携带previousValues。这与前文“类型订阅”一节描述的 payload 语义完全一致。

协议层:与文档五步流程的对应

SubscriptionProtocol.scala中定义了两种协议:

  • SubscriptionProtocolV05(协议名graphql-subscriptions):即本文“原生 WebSockets”一节所使用的协议,消息类型包括init/init_success/init_fail/subscription_start/subscription_end/subscription_success/subscription_fail/subscription_data/keepalive,与文档中的五步交互一一对应;
  • SubscriptionProtocolV07(协议名graphql-ws):更现代的订阅协议,消息类型为connection_init/connection_ack/connection_error/ka(keep-alive)/start/stop/data/error/complete

测试印证

仓库在server/servers/subscriptions/src/test/scala/com/prisma/subscriptions/specs/目录下提供了大量订阅相关测试,例如:

  • SubscriptionFilterSpec.scala:验证UPDATED订阅中previousValues支持枚举类型、订阅支持别名(alias)等过滤行为;
  • SubscriptionsProtocolV05Spec.scalaSubscriptionsProtocolV07Spec.scala:分别验证两种协议的完整消息交互流程;
  • SubscriptionsManagerForModelSpec.scala:验证订阅管理器的启动、订阅登记与事件分发逻辑。

这些测试用例可以直接作为理解订阅 API 行为边界的参考:例如订阅查询可以使用 GraphQL 别名,枚举字段会出现在previousValues中,mutation_in支持UPDATED等取值。

小结

Prisma 1 的 GraphQL 订阅围绕CREATED/UPDATED/DELETED三种事件展开:每个对象类型自动生成类型订阅,配合mutation_innode过滤与updatedFields系列过滤条件,可以精确订阅“某个类型的某个节点在某类变更时”的通知;订阅通过专用 WebSocket 端点(graphql-subscriptionsgraphql-ws协议)传输,既可以借助 GraphQL Playground、Apollo Client 等现成工具,也可以基于原生 WebSocket 按五步流程自行实现。仓库中server/servers/subscriptions模块的实现则揭示了其底层架构:模型级订阅管理器、基于消息总线的create/update/delete{Model}事件通道、35ms 的一致性缓冲以及分组复用查询的优化策略。掌握这套 API 与实现原理,即可在基于 Prisma 1 的应用中构建实时通知、在线协作、实时看板等数据驱动场景。

【免费下载链接】prisma1💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]项目地址: https://gitcode.com/gh_mirrors/pr/prisma1

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 5:32:28

SpringBoot+Vue水产养殖数字化系统设计与实践

1. 项目背景与行业痛点水产养殖作为传统农业的重要组成部分,近年来正经历着从粗放式管理向精细化运营的数字化转型。我在广东湛江对虾养殖基地实地调研时发现,大多数中小型养殖场仍在使用纸质记录本管理投喂、用药、水质等关键数据,这种管理方…

作者头像 李华
网站建设 2026/9/23 5:31:40

Mac本地部署Qwen Coder:从零开始玩转开源AI编程模型

最近后台一直有人问我“coder”这个词到底怎么回事,有人说想下载用用,有人说看别人在Mac上跑得飞起,还有人在搜“kh coder”结果搜出一堆和编程不相关的东西。这里先统一说明一下,如果是指AI代码生成工具,现阶段大家讨…

作者头像 李华
网站建设 2026/9/23 5:31:21

Claude-Code:终端原生的AI编程工作流实战指南

1. 项目概述:这不是一个“工具”,而是一套终端环境下的AI编程工作流“claude-code”这个名称在当前技术社区里,已经悄然脱离了单纯指代某个可执行文件的范畴。它实际代表的,是一整套围绕Anthropic Claude 模型能力、深度嵌入开发者…

作者头像 李华
网站建设 2026/9/23 5:29:28

PHP实现本地图片随机展示功能开发指南

1. 项目概述与核心思路 这个PHP项目实现了一个简单的本地图片随机展示功能,特别适合刚接触PHP开发的新手练手。它的核心逻辑是通过PHP脚本从指定文件夹中随机选取一张图片并输出到网页上。这种"轮播图"或"随机展示"的功能在网站开发中非常常见…

作者头像 李华
网站建设 2026/9/23 5:28:37

Python第五次作业解析:函数、文件与面向对象实战

1. Python第五次作业解析与实战指南作为Python课程的第五次作业,这次任务通常标志着学习者开始接触更复杂的编程概念。根据常见教学进度推测,这次作业可能涉及函数封装、文件操作或基础算法实现等核心知识点。让我们从实际教学经验出发,拆解这…

作者头像 李华
网站建设 2026/9/23 5:28:30

全面屏iPad Pro生产力跃进:A12X与Apple Pencil如何重塑移动办公

1. 从一台平板到一台“电脑”的野心第一次把 2018 款全面屏 iPad Pro 拿在手里的时候,我脑子里冒出来的第一个念头不是“这屏幕真大”,而是“苹果这次是认真的”。作为一个从 iPad 2 时代就开始折腾平板生产力的人,我太清楚过去那些年 iPad 在…

作者头像 李华