从 Graphcool Framework 迁移到 Prisma:Hooks、Resolver 函数与服务端订阅(Functions)实战指南
2026/9/23 17:57:14 网站建设 项目流程
  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

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

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

本文基于 docs/1.2/04-Reference/09-Migration-Guides/04-Functions.md 展开。Graphcool Framework 中的Hooks(同步数据校验与转换)、Resolver Functions(扩展 CRUD API 能力)与服务端订阅(Server-side Subscriptions)三大函数机制,在迁移到 Prisma 时有着清晰的对应改造路径:前两者从"托管函数"形态下放为GraphQL 应用层(Application Layer)的普通 resolver,后者则统一收敛为基于webhook的事件订阅。读完本文,你将掌握这三类函数在两种架构下的等价实现方式,并能在自己的 GraphQL Server 中落地可运行的迁移方案。

迁移前提:理解 Prisma 的两层 GraphQL 架构

在开始逐项迁移之前,需要先建立 Prisma 的架构心智模型。如 Migration Guides 总览 所述,用 Prisma 构建 GraphQL 服务器时,你的服务由两个 GraphQL 层组成:

  1. 数据库层(Database Layer):由 Prisma 提供,本质上是 GraphQL 查询引擎,对外暴露基于数据模型自动生成的通用 CRUD API(可类比 Graphcool 的 Simple & Relay API 的合并)。
  2. 应用层(Application Layer):这是 Graphcool 时代没有的新概念。应用层定义暴露给客户端的另一个 GraphQL API(即 application schema),业务逻辑、认证、权限、文件处理等全部在此层实现。

迁移的核心结论是:过去写在 Graphcool 函数(Hooks / Resolvers)里的逻辑,现在全部转移到应用层的传统 GraphQL resolver 中实现,并通过prisma-binding之类的库把请求转发给底层的 Prisma API。下图描述了这一职责迁移关系:

Graphcool Framework Prisma ───────────────── ────── Hook 函数(数据校验/转换) ──→ 应用层 createUser 等 resolver Resolver 函数(扩展 CRUD) ──→ 应用层自定义 root field 的 resolver 托管函数/Webhook 订阅 ──→ 仅 Webhook 形式(subscriptions 配置)

下面分别针对三类函数展开。

Hooks:数据校验与转换逻辑迁移到 resolver

Hooks 在 Graphcool Framework 中用于同步的数据校验(Data Validation)与数据转换(Data Transformation)。你可以把某个 hook 关联到 GraphQL API 的某个 mutation 上:在执行 mutation 之前,Graphcool 会先执行 hook 函数,它可以转换传入的 mutation 参数,也可以在校验规则不满足时抛错。

为便于对照,下文统一使用如下数据模型与 application schema:

type User { id: ID! @unique name: String! }
type Query { users: [User!]! } type Mutation { createUser(name: String!): User! }

数据校验(Data Validation)

示例场景:不允许创建name少于两个字母的User节点。

Graphcool Framework 中的写法

在 hook 函数中实现该校验,并把它关联到 Graphcool GraphQL API 的createUsermutation:

event => { if (event.data.name.length < 2) { return { error: `The provided name '${event.data.name}' is too short. A name must have at least two letters.` } } return { data: event.data } }

这里的关键点是:hook 通过返回{ error: ... }来拒绝请求,通过返回{ data: event.data }放行(必要时可同时改写数据)。事件对象event.data携带的是即将写入的 mutation 参数。

Prisma 中的等价实现

在 Prisma 架构下,该校验移入应用层——即你的 GraphQL Server 实现内部。更准确地说,校验需要在createUser的 resolver内部完成,不满足约束时直接throw一个 Error:

function createUser(parent, { name }, context, info) { if (name.length < 2) { throw new Error(`The provided name '${name}' is too short. A name must have at least two letters.`) } return context.db.mutation.createUser({ data: { name }, info) }

与 hook 的"返回 error 对象"不同,Prisma 方案直接利用 GraphQL resolver 的异常机制。注意代码中context.db即 Prisma 客户端实例(通过 prisma-binding 或 prisma-client-lib 生成),最终写库操作仍委托给 Prisma 的数据库层。

数据转换(Data Transformation)

数据转换的思路类似:原本在 hook 函数中的转换逻辑,现在同样下放到应用层。

示例场景:只把name字段的值以全大写形式存储。

Graphcool Framework 中的写法
event => { const uppercaseName = event.data.name.toUppercase() return { data: uppercaseName } }
Prisma 中的等价实现

createUserresolver 中先转换再写库:

function createUser(parent, { name }, context, info) { const uppercaseName = name.toUppercase() return context.db.mutation.createUser({ data: { name: uppercaseName }, info) }

细节提示:原文档两处示例中的toUppercase()是 Graphcool 时代文档沿用的笔误,JavaScript 标准 API 应为toUpperCase();同时createUser({ data: { name }, info)末尾缺少一个右括号},实际书写时应补全为createUser({ data: { name }, info })。在迁移到你自己的代码库时请按修正后的写法使用。

Resolver Functions:自定义 root field 下放为普通 resolver

Graphcool Framework 中的 Resolver Functions 用于扩展自动生成的 CRUD API 能力。典型场景包括:认证(如signuploginmutation)、集成第三方服务、包装 REST API。

下文以"包装 REST API"为例展开(用例源自 Graphcool Framework 的rest-wrapper示例),目标端点采用https://dog.ceo/api/breed/${breedName}/images/random——根据犬种名随机返回一张该犬种的图片 URL。

Graphcool Framework 中的写法

在 Graphcool Framework 中,一个 resolver function 由两部分组成:

  • SDL 编写的 schema extension:扩展Query类型并定义新的 root field,供客户端发起查询
  • JavaScript 实现的 resolver:处理逻辑

schema extension 如下:

type RandomBreedImagePayload { url: String! } extend type Query { randomBreedImage(breedName: String!): RandomBreedImagePayload! }

resolver 实现则从事件对象event中取出breedName参数,调用上述 REST 端点,并确保返回数据符合RandomBreedImagePayload的结构:

require('isomorphic-fetch') module.exports = event => { const { breedName } = event.data const url = `https://dog.ceo/api/breed/${breedName}/images/random` return fetch(url) .then(response => response.json()) .then(responseData => { const randomBreedImageData = responseData.message const randomBreedImage = { url: randomBreedImageData } return { data: randomBreedImage } }) }

注意 Graphcool 的 resolver 通过module.exports = event => ...的形式导出,且返回结构需要包一层{ data: ... }

Prisma 中的等价实现

与 Hooks 类似,Graphcool 的 resolver function 能力现在在应用层实现,不再由 Prisma 直接托管。

首先,application schema 中同样需要定义对应的 root field:

type RandomBreedImagePayload { url: String! } extend type Query { randomBreedImage(breedName: String!): RandomBreedImagePayload! }

然后,resolver 只是你的 GraphQL Server 实现中的一个普通 resolver 函数(这里用对象方法简写形式定义,匿名函数同样适用):

function(parent, { breedName }, context, info) { const url = `https://dog.ceo/api/breed/${breedName}/images/random` return fetch(url) .then(response => response.json()) .then(responseData => { const randomBreedImageData = responseData.message const randomBreedImage = { url: randomBreedImageData } return { data: randomBreedImage } }) }

与 Graphcool 版本相比,Prisma 版本不再需要event.data解包,breedName直接作为 GraphQL 标准 resolver 的第二个参数(args)传入;返回结构也遵循 GraphQL 标准——直接返回对象,由 schema 约束其形状。

服务端订阅(Server-side Subscriptions):收敛为 Webhook 交付

服务端订阅在 Prisma 中遵循同样的订阅概念,但有一个关键差异:Prisma 不再支持将对应函数托管为 managed functions(Graphcool 中的托管函数),而是必须通过webhook配置,指向你自己部署的 HTTP 端点(例如 AWS Lambda、Google Cloud Functions、Zeit Now 等)。下文示例源自 Graphcool Framework 的subscriptions示例。

Graphcool Framework 中的写法

在 Graphcool Framework 中配置一个服务端订阅,需要提供两个组件:

  • GraphQL 订阅查询(subscription query):定义订阅什么事件、事件发生时接收哪些数据
  • 处理器(handler):事件发生时被调用——既可以是 managed function,也可以是 webhook

以下订阅查询表达的是:当一个新的User节点被创建时触发 handler,事件载荷携带新Useridname

createFirstArticle.graphql

subscription { User(filter: { mutation_in: [CREATED] }) { node { id name } } }

接着把 handler 指定为 managed function:

createFirstArticle.js

const { fromEvent } = require('graphcool-lib') module.exports = event => { // Retrieve payload from event const { id, name } = event.data.User.node // Create Graphcool API (based on https://github.com/graphcool/graphql-request) const graphcool = fromEvent(event) const api = graphcool.api('simple/v1') // Create variables for mutation const title = `My name is ${name}, and this is my first article!` const variables = { authorId: id, title } // Create mutation const createArticleMutation = ` mutation ($title: String!, $authorId: ID!) { createArticle(title: $title, authorId: $authorId) { id } } ` // Send mutation with variables return api.request(createArticleMutation, variables) }

这里 handler 收到的event具有如下结构(与订阅查询的形状一致):

{ "data": { "User": { "node": { "id": "cj8wscby6nl7u0133zu7c8a62", "name": "Sarah" } } } }

在 Graphcool 中,managed function 配置在graphcool.yml里,指向订阅查询文件与实现文件:

functions: createFirstArticle: type: subscription query: src/createFirstArticle.graphql handler: code: src/createFirstArticle.js

Prisma 中的等价实现

在 Prisma 中,你仍然在服务根配置文件(prisma.yml)中配置订阅,但 YAML 键名不同,且 handler 只能指向 webhook:

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

该示例假定你已经把一个 serverless 函数部署到了端点https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev/createFirstArticle——订阅触发时,Prisma 会通过 HTTP 调用该端点,并把订阅查询的结果作为请求载荷发送过去。因此,原先写在 managed function 里的逻辑(读取event.data.User.node、调用createArticlemutation 等)需要迁移到该 webhook 端点对应的函数代码中。

深入 Prisma 的subscriptions配置细节(源码视角)

为了让上文的 Prisma 配置真正可用,有必要精确理解prisma.ymlsubscriptions的完整语法。这一部分在两个地方有官方依据:

配置结构与两种 webhook 写法

依据 服务配置参考 · YAML 结构 中subscriptions(optional)一节,subscriptions属性用于定义服务的全部事件订阅函数,每个订阅需要(至少)两类信息:

  • 订阅查询:定义哪个事件触发函数、载荷长什么样
  • webhook 的 URL:事件发生时通过 HTTP 调用的地址
  • (可选)HTTP headers:附加到发往该 URL 的请求上

其类型为对象,包含以下属性:

属性必填说明
query订阅查询文件的路径
webhook要调用的 webhook 信息。无 headers 时可直接给 URL 字符串;需要 headers 时为一个含urlheaders的对象

不带 HTTP headers 的写法(webhook直接给字符串):

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

带两个 HTTP headers 的写法(webhook为对象,支持${env:...}变量):

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

CLI 端如何解析这些配置

prisma.yml的解析在 CLI 包 prisma-yml 中完成。以 PrismaDefinition.ts 的getSubscriptions()方法为例,从源码可以确认:

  • webhook字段确实支持两种形态:当它是字符串时直接作为 URL,且无 headers(headers为空数组);当它是对象时取webhook.url,并通过transformHeaders(subscription.webhook.headers)处理 headers;
  • query字段支持文件路径与内联查询两种形态:若以.graphql结尾,则视为相对于prisma.yml所在目录(definitionDir)的文件路径,读取其内容作为订阅查询;读取前会做存在性校验,文件不存在时抛出形如Subscription query <path> provided in subscription "<name>" in prisma.yml does not exist.的错误;
  • 最终每个订阅被归一化为{ name, query, headers, url }的结构,供部署流程使用。

另外,订阅查询还允许直接内联在prisma.yml中(此时query后面跟的是|块标量),例如服务端订阅参考文档 Server-side Subscriptions Overview 中的示例:

service: my-service stage: ${env:PRISMA_STAGE} secret: ${env:PRISMA_SECRET} cluster: ${env:PRISMA_CLUSTER} datamodel: database/datamodel.graphql subscriptions: userChangedEmail: webhook: url: http://example.org/sendSlackMessage headers: Content-Type: application/json Authorization: Bearer cha2eiheiphesash3shoofo7eceexaequeebuyaequ1reishiujuu6weisao7ohc query: | { user({ where: { mutation_in: [UPDATED], updatedFields_contains: "email" } }) { name email } }

订阅查询语法的演进差异

从上述内联示例可以看到,Prisma 服务端订阅的查询语法已经与 Graphcool 时代的User(filter: { mutation_in: [CREATED] })不同:Prisma 使用小写的根字段(如user),并改用where: { mutation_in: [...] }作为过滤条件。也就是说,迁移订阅查询时,除了把graphcool.yml换成prisma.yml、把functions键换成subscriptions键、把handler.code换成webhook之外,订阅查询本身也需要按 Prisma 的 API 语法改写。该文档还提到,服务端订阅在能力上等价于普通 GraphQL subscriptions(支持同样的过滤条件),区别仅在于交付机制:Prisma 会监测数据变更并在适用时执行关联查询,然后通过 webhook 把结果投递出去。

迁移清单:三大函数的对照速查

能力Graphcool FrameworkPrisma
数据校验(Hook)hook 函数关联 mutation,返回{ error }拒绝应用层 resolver 内throw new Error(...)
数据转换(Hook)hook 函数返回{ data: 转换后数据 }应用层 resolver 内先转换再委托 Prisma 写库
扩展 CRUD(Resolver Function)SDL schema extension +module.exports = event => ...托管函数application schema 定义 root field + 普通 GraphQL resolver
服务端订阅managed function 或 webhook 两种 handler仅 webhook 形式(prisma.ymlsubscriptions键)
订阅查询语法User(filter: { mutation_in: [CREATED] })user(where: { mutation_in: [CREATED] })等 Prisma 语法

综合来看,从 Graphcool Framework 迁移到 Prisma 时,函数机制的总体方向是:把"平台托管的业务逻辑"搬回自己的应用代码。Hooks 与 Resolver Functions 对应为应用层的 GraphQL resolver(业务逻辑、校验、第三方集成都留在你的 GraphQL Server 中),服务端订阅则简化为"订阅查询 + webhook 端点"的纯事件通知模型。理解并应用这一范式转换,是平滑完成迁移的关键。

  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

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

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询