- 后端
- 数据库
- GraphQL
【免费下载链接】prisma1
💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]
本文基于 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 层组成:
- 数据库层(Database Layer):由 Prisma 提供,本质上是 GraphQL 查询引擎,对外暴露基于数据模型自动生成的通用 CRUD API(可类比 Graphcool 的 Simple & Relay API 的合并)。
- 应用层(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 能力。典型场景包括:认证(如signup、loginmutation)、集成第三方服务、包装 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,事件载荷携带新User的id与name:
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.jsPrisma 中的等价实现
在 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.yml中subscriptions的完整语法。这一部分在两个地方有官方依据:
配置结构与两种 webhook 写法
依据 服务配置参考 · YAML 结构 中subscriptions(optional)一节,subscriptions属性用于定义服务的全部事件订阅函数,每个订阅需要(至少)两类信息:
- 订阅查询:定义哪个事件触发函数、载荷长什么样
- webhook 的 URL:事件发生时通过 HTTP 调用的地址
- (可选)HTTP headers:附加到发往该 URL 的请求上
其类型为对象,包含以下属性:
| 属性 | 必填 | 说明 |
|---|---|---|
query | 是 | 订阅查询文件的路径 |
webhook | 是 | 要调用的 webhook 信息。无 headers 时可直接给 URL 字符串;需要 headers 时为一个含url与headers的对象 |
不带 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/jsonCLI 端如何解析这些配置
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 Framework | Prisma |
|---|---|---|
| 数据校验(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.yml的subscriptions键) |
| 订阅查询语法 | 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]
相关推荐
Graphcool 到 Prisma 迁移指南:Functions(Hooks、Resolver 与服务端订阅)落地实践
Graphcool 到 Prisma 迁移指南:Functions(Hooks、Resolver 与服务端订阅)落地实践 本篇指南讲解如何把 Graphcool
后端数据库GraphQLGraphcool 迁移到 Prisma:Hooks、Resolver Functions 与 Server-side Subscriptions 函数能力迁移完整指南
Graphcool 迁移到 Prisma:Hooks、Resolver Functions 与 Server side Subscriptions 函数能力迁移
后端数据库GraphQLPrisma 迁移指南(四):从 Graphcool Framework 迁移函数能力(Hooks、Resolver Functions 与 Server-side Subscriptions)
Prisma 迁移指南(四):从 Graphcool Framework 迁移函数能力(Hooks、Resolver Functions 与 Server si
后端数据库GraphQL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考