news 2026/9/23 2:39:40

Prisma 服务端订阅(Server-side Subscriptions)实战:用 Webhook 把数据库变更推送给 Serverless 应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Prisma 服务端订阅(Server-side Subscriptions)实战:用 Webhook 把数据库变更推送给 Serverless 应用
  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]

项目地址:https://gitcode.com/gh_mirrors/pr/prisma1
点击查看免费下载

导读

本文讲解 Prisma(当前仓库 prisma1,即 deprecated 的 Prisma 1.x 开源版本)中的**服务端订阅(Server-side Subscriptions)**机制:它拥有与普通 GraphQL Subscriptions 等价的过滤能力,但事件不是通过 WebSocket 推送给订阅客户端,而是由 Prisma 服务端在数据变更时执行订阅查询,并把查询结果以Webhook的形式通过 HTTP 推送到你指定的 URL。读完本文,你将掌握在prisma.yml中配置服务端订阅的完整写法(含内联 query、.graphql文件、自定义 headers 等变体),理解其触发与投递的底层执行链路,并能从源码与集成测试层面验证其行为边界。

服务端订阅:与 GraphQL 订阅等价、交付方式不同

能力等价:同样的过滤 API

原文档明确指出:服务端订阅在能力上与普通 GraphQL 订阅完全等价。这意味着你可以使用与客户端 GraphQL 订阅完全相同的过滤语法,例如:

  • mutation_in: [CREATED, UPDATED, DELETED]:只在感兴趣的事件类型上收到通知;
  • node内层过滤:对变更节点的字段值进行约束;
  • previousValues:读取变更前的旧值。

Prisma 会持续监控数据变更,当一次 mutation 发生且满足订阅查询中的过滤条件时,就执行该订阅查询——这与普通 GraphQL 订阅的触发逻辑一致,区别仅在于结果如何送达

对比维度普通 GraphQL 订阅服务端订阅(Server-side Subscriptions)
过滤 API相同(where等参数)相同
触发时机数据变更时由 Prisma 监控并执行查询数据变更时由 Prisma 监控并执行查询
交付方式通过 WebSocket 推送给在线订阅客户端通过 HTTP Webhook 投递到配置的 URL
目标场景浏览器 / 实时前端无状态的后端服务、Serverless 函数

为 Serverless 而生

服务端订阅的设计初衷是与现代的Serverless 基础设施协同工作:Serverless 函数(如 AWS Lambda)是无状态的、无法维持长连接,因此基于 WebSocket 的订阅模型并不适合直接对接。Webhook 模型让 Prisma 可以在事件发生时发起一次普通的 HTTP 请求,Serverless 函数只需要暴露一个 HTTP 端点即可接收事件。

从本仓库的实现看,当前版本支持通过webhook交付事件;文档同时预告了未来会加入对 AWS Lambda 直接调用以及不同消息队列实现的支持(当前以 webhook 为主要交付通道,相关队列基础设施可参见 PrismaLocalDependencies.scala 与 PrismaProdDependencies.scala 中的webhookPublisher/webhooksConsumer定义)。

配置服务端订阅:prisma.yml 中的 subscriptions 属性

服务端订阅的配置入口是服务根目录下的prisma.yml文件,通过subscriptions属性声明。该属性在prisma.yml的 YAML 结构文档 中被明确定义为**可选(optional)**对象,每个订阅至少需要两部分信息:

  1. 订阅查询(subscription query):定义在何种事件上触发函数、以及回调负载(payload)长什么样;
  2. webhook 的 URL:事件发生时通过 HTTP 调用的地址;
  3. (可选)若干 HTTP headers:附加到发送到该 URL 的请求上。

完整配置示例(原文档)

以下配置声明了一个名为userChangedEmail的服务端订阅:当任意user节点发生UPDATED变更时,Prisma 执行订阅查询,并把查询结果 POST 到http://example.org/sendSlackMessage,同时携带Content-TypeAuthorization两个请求头:

endpoint: ${env:PRISMA_ENDPOINT} secret: ${env:PRISMA_SECRET} datamodel: database/datamodel.graphql subscriptions: userChangedEmail: webhook: url: http://example.org/sendSlackMessage headers: Content-Type: application/json Authorization: Bearer cha2eiheiphesash3shoofo7eceexaequeebuyaequ1reishiujuu6weisao7ohc query: | subscription { user(where: { mutation_in: [UPDATED] }) { node { name email } } }

要点解读:

  • subscriptions的每个键(这里是userChangedEmail)是订阅的自定义名称,也是投递到 webhook 时标识函数身份的functionName
  • query可以像上面这样内联写在prisma.yml中,也可以指向一个.graphql文件(见下文);
  • webhook需要提供url与可选的headers;headers 常用于鉴权(如Authorization)与告知接收方内容类型。

触发示例

上面配置的userChangedEmail订阅,会在执行如下 mutation 时被触发:

mutation { updateUser( data: { email: "new@email.com" }, where: { id: "cjcgo976g5twb018740bzyy4q" } ) { id } }

该 mutation 把 id 为cjcgo976g5twb018740bzyy4quseremail更新为new@email.com。因为订阅过滤条件mutation_in: [UPDATED]命中了UPDATED类型,Prisma 会执行订阅查询,webhook 负载中包含node(更新后的nameemail)。

subscriptions 属性的完整语法与变体

结合 prisma.yml 的 YAML 结构参考,subscriptions的完整用法如下:

类型定义

  • query必填):订阅查询的文件路径,或内联的 GraphQL 订阅字符串;
  • webhook必填):要调用的 webhook 信息(URL 与可选 headers)。如果没有 headers,可以直接把 URL 字符串赋给webhook属性;如果带 headers,则webhook需是一个含urlheaders的对象。

变体一:无 headers,直接使用 URL 字符串

subscriptions: sendWelcomeEmail: query: database/subscriptions/sendWelcomeEmail.graphql webhook: https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev/sendWelcomeEmail

变体二:带多个 headers

subscriptions: sendWelcomeEmail: query: database/subscriptions/sendWelcomeEmail.graphql webhook: url: https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev/sendWelcomeEmail headers: Authorization: ${env:MY_ENDPOINT_SECRET} Content-Type: application/json

变体三:与 custom 变量结合

subscriptions中的值同样支持 变量引用(如${env:...}${self:custom...}),便于复用端点地址与查询目录:

custom: serverlessEndpoint: https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev subscriptionQueries: database/subscriptions/ subscriptions: sendWelcomeEmail: query: ${self:custom.subscriptionQueries}/sendWelcomeEmail.graphql webhook: https://${self:custom.serverlessEndpoint}/sendWelcomeEmail

源码级剖析:从 prisma.yml 到 webhook 投递的完整执行链路

1. CLI 侧:订阅配置的解析

Prisma CLI 在部署时读取prisma.yml并解析订阅定义,核心逻辑位于 cli/packages/prisma-yml/src/PrismaDefinition.ts 的getSubscriptions()方法(PrismaDefinition.ts 第 318-352 行):

  • 遍历subscriptions对象的每个键值对;
  • 兼容webhook的两种写法:若webhook是字符串则直接作为 URL,若是对象则取.url.headers(headers 会被transformHeaders归一化处理);
  • query做处理:如果query.graphql结尾,则视为相对prisma.yml所在目录的文件路径,读取该文件内容作为查询(文件不存在会抛出明确错误:Subscription query ... provided in subscription ... in prisma.yml does not exist);否则视为内联查询字符串;
  • 最终产出{ name, query, headers, url }的订阅描述供部署使用。

2. 服务器侧:触发与执行

当一次 mutation 在数据库中产生变更后,API 服务器会生成副作用 mutaction(side-effect mutaction),由 SideEffectMutactionExecutor.scala 分派执行(第 22-25 行):

def execute(mutaction: SideEffectMutaction): Future[Unit] = mutaction match { case mutaction: PublishSubscriptionEvent => PublishSubscriptionEventExecutor.execute(mutaction, apiDependencies.sssEventsPubSub) case mutaction: ExecuteServerSideSubscription => ServerSideSubscriptionExecutor.execute(mutaction) }

其中ServerSideSubscriptionExecutor.execute(第 37-75 行)是服务端订阅的核心:

  1. 根据function.delivery的形态判断交付方式,WebhookDelivery走 webhook 分支;
  2. 调用SubscriptionExecutor.execute内存中执行订阅查询,注意两个关键参数:
    • skipPermissionCheck = true:服务端订阅的查询不受普通 API 权限检查限制(由服务端自动执行);
    • alwaysQueryMasterDatabase = true:始终查询主数据库,保证读到最新数据;
  3. 只有当执行结果包含data键(即订阅查询有匹配结果)时,才构造Webhook并发布到webhookPublisher队列:
    • urlheaders直接来自prisma.yml中的配置;
    • payload是订阅查询的 JSON 结果字符串;
    • projectIdfunctionName(即prisma.yml中订阅的名字)、requestId用于追踪标识;
  4. 若查询结果为空(过滤器未匹配),则不发布任何 webhook

Webhook的数据结构定义在 server/servers/api/src/main/scala/com/prisma/subscriptions/Webhook.scala:包含projectIdfunctionNamerequestIdurlpayloadidheaders七个字段。

3. 过滤器评估与结果判定

server/servers/api/src/main/scala/com/prisma/subscriptions/SubscriptionExecutor.scala 揭示了“订阅是否触发”的判定逻辑(第 72-104 行):

  • 用本次 mutation 的类型、updatedFieldspreviousValues构建内部订阅 schema(SubscriptionSchema);
  • 通过QueryTransformer.evaluateInMemoryFiltersVariablesTransformer.evaluateInMemoryFilters先在内存中评估过滤条件(如mutation_innode字段约束);
  • 只有filtersMatch && variablesMatch都成立时,才用 Sangria Executor 真正执行转换后的订阅查询;
  • 执行完毕后,在结果中查找与模型名对应的顶层字段(如user),若该字段值为null(例如node: null),则整体视为未匹配、不投递 webhook(第 127-130 行)。

测试验证:webhook 在各种变更类型下的行为

仓库中的集成测试 server/servers/api/src/test/scala/com/prisma/subscriptions/EmbeddedServerSideSubscriptionSpec.scala 通过内存 webhook 队列(webhookTestKit)完整验证了服务端订阅的行为,这些用例可以帮助你理解并预测生产环境中的实际表现:

场景行为
createTodo创建节点,命中CREATED订阅发布 1 个 webhook,payload 中node为新建节点数据、previousValuesnull
updateTodo更新节点,命中UPDATED订阅发布 1 个 webhook,payload 同时包含更新后的nodepreviousValues(旧值)
deleteTodo删除节点,命中DELETED订阅发布 1 个 webhook,payload 中nodenullpreviousValues为删除前的值
嵌套变更(如createTodo内嵌createcomment,命中comment订阅)同样发布 1 个 webhook,node.comments包含嵌套创建的节点
变更不满足过滤条件(如status: DONE不匹配node.status: ACTIVE过滤)不发布任何 webhook

从测试断言还可以看到两个重要事实:

  1. webhook 的 headers 会原样传递:测试配置headers = Vector("header" -> "value"),最终断言webhook.headers == Map("header" -> "value")
  2. payload 即为订阅查询的 JSON 结果:如更新场景下 payload 形如{"data": {"todo": {"node": {...}, "previousValues": {...}}}},与你在订阅查询中声明的字段选择完全一致。

部署与使用注意事项

  • webhook 端点必须是可公网访问的 HTTP(S) 地址:Prisma 服务器会主动向该地址发起请求,本地localhost仅在 Prisma 与接收方同机部署时可用;
  • secret 与鉴权:推荐在prisma.yml顶层配置secret(如secret: ${env:PRISMA_SECRET})保护 API 与订阅配置,并通过 webhook 的headers携带接收方所需的鉴权凭证(如Authorization: Bearer ...);
  • payload 形状与字段选择一致:订阅查询中声明的字段决定了 webhook 负载内容;如需previousValues,必须在查询中显式声明该字段;
  • 匹配失败不投递:订阅查询过滤条件未命中时不会产生 webhook 请求(源码层面由filtersMatch/variablesMatch与结果判空共同保证),因此不用担心无关变更带来的噪声流量;
  • 交付通道现状:本仓库版本以 webhook 为唯一交付实现(WebhookDelivery分支),源码中保留了其他交付类型的兜底分支(case _ => Future.unit),为后续扩展 AWS Lambda 直接调用与队列实现预留了位置。

参考文档与源码索引

  • 本文主体文档:docs/1.12/04-Reference/04-Server_side-Subscriptions/01-Overview.md
  • prisma.yml完整结构与subscriptions属性说明:docs/1.12/04-Reference/02-Service-Configuration/02-prisma.yml/02-YAML-Structure.md
  • prisma.yml概览与示例(含订阅与目录结构):docs/1.12/04-Reference/02-Service-Configuration/02-prisma.yml/01-Overview-&-Example.md
  • CLI 订阅配置解析:cli/packages/prisma-yml/src/PrismaDefinition.ts
  • 服务器侧执行与投递:server/servers/api/src/main/scala/com/prisma/api/mutactions/SideEffectMutactionExecutor.scala
  • 订阅过滤器评估与执行:server/servers/api/src/main/scala/com/prisma/subscriptions/SubscriptionExecutor.scala
  • Webhook 数据结构:server/servers/api/src/main/scala/com/prisma/subscriptions/Webhook.scala
  • 集成测试(行为验证):server/servers/api/src/test/scala/com/prisma/subscriptions/EmbeddedServerSideSubscriptionSpec.scala
  • 后端
  • 数据库
  • GraphQL

【免费下载链接】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 2:34:59

食品干燥技术:热风与红外耦合的Comsol仿真实践

1. 食品干燥技术概述食品干燥是食品加工中最基础也最关键的环节之一。作为一名在食品工程领域摸爬滚打十多年的从业者,我深知干燥工艺对食品品质的决定性影响。传统热风干燥虽然设备简单、成本低廉,但普遍存在能耗高、时间长、营养损失大等问题。而红外干…

作者头像 李华
网站建设 2026/9/23 2:34:10

瞳孔虹膜分割数据集实战:从标注验证到Unet/YOLO-seg训练全流程解析

简介:面向医学图像分割与计算机视觉研究者,提供一套高分辨率瞳孔与虹膜分割数据集。数据集中于人眼区域图像,统一为640640分辨率,标注图采用灰度mask格式,像素值0、1、2分别对应背景、瞳孔和虹膜,可直接用于…

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

AI服务的SLO与影子流量-在上线前证明它没变笨

摘要 传统服务的 SLO 通常围绕可用性与延迟,用在 AI 服务上会漏掉最重要的一环:质量。一个响应快、从不报错但答案越来越差的系统,在传统监控下表现完美。本文拆解 AI 服务应当定义的四类 SLO、为什么尾延迟比平均延迟更重要、影子流量如何用…

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

Akka 与 GraalVM Native Image:构建本地可执行文件的完整指南

Akka 与 GraalVM Native Image:构建本地可执行文件的完整指南 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak/a…

作者头像 李华