- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
导读
PostGraphile V5 提供了 JavaScript 与 SQL 双路径的 API 定制能力:你可以从数据库对象(表、视图、函数、约束等)出发做注解式定制,也可以通过配置文件(Preset)调整全局行为,还可以用插件机制深度扩展 GraphQL Schema。本文以官方customization-overview文档为主线,结合仓库中 presets 与插件工厂的源码实现,系统梳理三大定制工具的用法、内置 Preset 的差异以及常见定制任务的推荐做法,读完你即可为项目规划出一条从数据库到 GraphQL 的完整定制路线。
三大定制工具:先理解整体框架
PostGraphile 的核心工作是从数据库自动推导出 GraphQL API,因此首要前提是数据库中要有合适的对象(表、列、类型、约束、函数、视图、索引等)。PostGraphile 能适配多种形态的数据库,但官方明确建议:不要回避数据库本身强大的特性。
在数据库对象之上,官方给出了三个层次的定制工具:
| 工具 | 作用层次 | 典型入口 |
|---|---|---|
| Annotations(注解) | 数据库对象 → 生成结果 | 数据库 “smart comments”、postgraphile.tags.json5 |
| Configuration(配置) | 全局行为 | graphile.config.ts中的 Preset |
| Extension(扩展) | Schema 结构与执行逻辑 | 插件,尤其是extendSchema插件工厂 |
:::tip 如果你要从 JavaScript/TypeScript 添加字段或类型,第一站是学习extendSchema——它是官方推荐的“Schema 加法”插件工厂。 :::
数据库对象:定制的第一层
PostGraphile 鼓励你把精力放在数据库设计上,它会尽力从设计良好的数据库 Schema 中自动提取最佳实践的 GraphQL API。设计时请务必遵循最佳实践:主键、唯一约束、外键约束、索引等一个都不能少。
当你向数据库新增实体且相关权限已GRANT时,PostGraphile 会自动生成对应的字段与类型——这也是为什么很多定制“什么都不用做”就能生效。
表、视图、物化视图与约束
对于数据库中找到的表、视图和物化视图,PostGraphile 会自动构建以下内容:
- 用于获取全部行的根 Query 字段;
- 通过主键和唯一约束获取单行的根 Query 字段;
- 反映外键约束的、位于引用方与被引用方类型上的关系字段;
- 用于创建、更新、删除这些记录的 Mutation 字段。
具体生成哪些内容,取决于:
- 数据库中的权限(
GRANT); - 表上定义的约束;
- 索引(主要影响反向关系);
- 配置项与全局默认行为(例如:默认倾向使用 connections(默认)还是 lists)。
这些默认决策可以通过 behavior 系统在全局、单表或单约束粒度上覆盖,例如在相关实体上加 smart tags,或者设置preset.schema.defaultBehavior。
:::tip 给视图添加“虚拟约束” 视图和物化视图原生不支持约束,因此 PostGraphile 提供了“虚拟约束”机制:你可以通过@primaryKey、@unique、@foreignKey等 smart tags 让视图表现得像拥有主键、唯一约束和外键约束一样。 :::
想深入了解表与视图的用法,可阅读 tables 和 views;外键约束如何形成类型间的 relations 关系,也有专门文档说明。
函数:从易失性推导暴露方式
向数据库添加函数时,PostGraphile 默认用启发式规则决定它在 Schema 中的暴露方式:
- 若函数是
volatile(CREATE FUNCTION的默认值),PostGraphile 假定它会改变数据,将其作为mutation暴露——即“custom mutation”函数; - 若函数声明为
STABLE或IMMUTABLE,则会被加入 query 操作,要么作为顶层字段(“custom query”函数),要么在满足特定条件时成为表类型上的字段(即“computed column”函数)。
:::info “Computed column”函数其实名不副实 “Computed column”函数最初的设计意图是让类型基于自身其他属性新增标量字段,但后来功能远超于此,如今它甚至可以返回整组相关记录……只是“computed column”这个名字一直沿用,目前尚未出现更好的替代(最接近的叫法是“custom field”)。请记住:computed column 函数并不局限于你想象中“列”那样的简单标量。 :::
三种函数的深入用法分别见 “custom query” 函数、“computed column” 函数 和 “custom mutation” 函数。
Annotations:用注解改写生成结果
Annotations 分为两类:
- Descriptions(描述):会写入生成的 GraphQL 类型/字段/参数等作为文档,你可以在 GraphiQL 等工具里直接读到;
- Smart tags(智能标签):影响 PostGraphile 以及你所用插件如何处理被注解的数据库对象。
Smart tag 的约定形态是:一个名字(按惯例以@为前缀)+ 一个值,值可以是布尔true、字符串或它们的列表。
几个高频示例:
@name:重命名任意函数、表或视图;@omit:从 API 中隐藏任意字段;@resultFieldName:重命名 mutation 的结果字段;- 视图(无法声明约束)可以通过
@foreignKey、@primaryKey、@unique添加类似约束的字段,使其像功能更完整的表一样工作。
Annotations 的来源可以组合使用:PostgreSQL 注释、JSON tags 文件、插件。
Smart comment 语法与解析规则
Smart comments 是 smart tags 最古老的载体,通过 PostgreSQL 的COMMENT语句写入。解析规则(见 smart-comments 文档)非常严格:
- 一个 smart comment 由若干 tag 和紧随其后的剩余注释组成;
- 开头空格和 tab 被忽略,首行为空行也会被忽略;
- tag 可以有字符串负载(跟在 tag 后、以空格分隔,不能包含换行),tag 之间用换行(
\n或\r\n)分隔; - tag 必须以
@开头,且必须位于剩余注释之前; - 无负载的 tag 值为布尔
true,有负载则为字符串;同一 tag 出现多次时,最终值为各值的数组。
例如下面这段注释:
@name meta @isImportant @jsonField date timestamp @jsonField name text @jsonField episode enum ONE=1 TWO=2 This field has a load of arbitrary tags.会解析出如下 tags 对象:
{ "name": "meta", "isImportant": true, "jsonField": ["date timestamp", "name text", "episode enum ONE=1 TWO=2"] }而最后一行(This field has a load of arbitrary tags.)则作为描述文档保留。官方推荐用美元符引号(dollar quoting)书写多行注释;注意,连续两个换行会把 smart tags 部分与描述正文分隔开,两个换行之后的内容即使以@开头也不会再被解析为 tag。
添加文档(描述)
你可以通过注解修改 GraphQL Schema 中类型或字段的描述。描述会显示在 GraphiQL 的文档面板中,也可能出现在编辑器和其它位置。默认情况下,PostGraphile 会从每条数据库COMMENT中提取全部 smart tags,并把剩余部分用作资源描述:
COMMENT ON TABLE users IS $$ @name people Represents the people that can log in to our application. $$;描述也可以通过postgraphile.tags.json5文件或插件提供。详见 “smart comments” 与 postgraphile.tags.json5。
Smart tags 的内置集合
Smart tags 不只这三个,仓库文档 smart-tags 收录了内置标签的非穷尽列表,这里摘录最常用的:
@name:作用于表、视图、物化视图、复合类型、列、类型、custom query 函数(Query 字段名)、custom mutation 函数(Mutation 字段名);@fieldName:作用于外键约束(本地关系字段名,参见@foreignFieldName)、唯一约束(根 finder 字段名)、computed column 函数(生成的字段名);@foreignFieldName/@foreignSimpleFieldName/@foreignConnectionFieldName:作用于外键约束在远端类型上的“反向”关系字段名,其中后两者分别精确覆盖 list 字段与 connection 字段的命名(该名字会按情况送入connectionField(默认什么都不做)或listField(默认追加List)inflector);@deprecated:作用于列,可将列标记为废弃(需要多行文本时可重复指定该 tag);@returnType:作用于 custom query / custom mutation / computed column 函数,指定表示函数结果的具名 GraphQL 类型(可与 list/connection/non-null 等包装组合;若函数返回多态类型,命名类型必须是该多态类型本身或其某个实现,官方提醒你需自行保证结果与该类型一致);@resultFieldName:作用于 custom mutation 函数,指定 mutation payload 类型上的字段名;@behavior:覆盖表、视图、物化视图、类型、列、约束、函数等实体的 behavior(见下文);@arg0variant、@arg1variant……:作用于 custom query / custom mutation / computed column 函数,将复合类型参数转换为patch(等价于update*mutation 的参数)、nodeId(接受类型的全局对象标识)或base(所有列都可用且可空)等“变体”类型,argN中的 N 是 0 起始的参数序号;@notNull:将列标记为非空(视图列常用);@primaryKey、@unique、@foreignKey:虚拟约束,让视图等无法声明约束的类型具备主键/唯一/外键行为(声明主键的列会自动被标记为@notNull;官方提醒声明的主键必须真的唯一,系统不会校验);@ref/@refVia:见 Refs。
json5 文件中的写法等价于 SQL 注释,例如@name的两种写法:
{ version: 1, config: { class: { post: { tags: { name: "message", }, }, }, procedure: { search_posts: { tags: { name: "returnPostsMatching", }, }, }, }, }comment on table post is E'@name message'; comment on function search_posts(text) is E'@name returnPostsMatching';Smart tags 的注入途径还有pgSmartTags实例,以及自定义插件:实现gather.hooks.pgIntrospection_introspection回调,拿到实体后调用entity.getTagsAndDescription(),再修改返回对象的.tags属性。
Behavior 系统:注解与配置的桥梁
New to V5 的 behavior 系统 为“哪些东西暴露、以何种方式暴露”提供了细粒度控制。它的核心是“behavior string”,例如:
insert+list -connection -list:filter-insert -update -delete query:*:filter +connection -list
每个行为字符串由空格分隔的“行为片段”组成,片段可选+/-前缀(省略时视为+),后跟由冒号连接的“scope 短语”(camelCase 单词或*)。最终行为由多个来源拼接而成,优先级从低到高大致为:插件默认行为 → 全局默认行为 → 插件推断行为 →(次要实体行为,如列的 codec 行为)→ 实体行为(smart tags/smart comments)。判定时系统从后向前扫描行为字符串,第一个匹配片段的-修饰符会否决该行为。
用@behavior注解即可覆盖实体的默认行为:
comment on table users is E'@behavior -insert -delete';全局默认行为则由preset.schema.defaultBehavior设置(详见下文配置部分)。仓库还提供了npx graphile behavior debug命令,可快速检查具体实体上哪些行为片段生效及原因。
Configuration:用 Preset 配置全局行为
PostGraphile 高度可配置,起点是 PostGraphile 配置文件,通常存放在graphile.config.ts(也支持.js、.mts、.cjs等)。这个文件定义你的配置“preset”,可以继承其它 preset、添加插件,并为这些插件和 preset 设置配置项。
文件中可用的配置项取决于它使用的插件和 preset——这正是官方建议使用 TypeScript 或graphile config options命令来探索可用选项的原因:该命令会尝试用 TypeScript language server 判断当前配置下可用的选项。常用选项的快速参考见 config 文档,例如pgServices用于声明要连接的 PostgreSQL 数据库(name与adaptor为必填,schemas、pgSettings、pgSubscriber等可选),日常场景优先用makePgService()辅助函数构建,而不是手写adaptorSettings。
Presets:可共享的配置单元
Preset 把其它 preset、插件和配置项组合成一个便于共享的 JS 对象。PostGraphile 提供了若干内置 preset(源码见 postgraphile/postgraphile/src/presets):
postgraphile/presets/amber:基础 preset,包含 PostGraphile 的大部分功能,是必选底座。从源码 amber.ts 可以看到,它通过extends继承graphileBuildPreset与graphileBuildPgPreset,并以orderedPlugins把PgTablesPlugin、PgRelationsPlugin、PgMutationCreatePlugin、PgMutationUpdateDeletePlugin、PgRowByUniquePlugin等插件排成与 PostGraphile V4 更兼容的顺序,最后追加SwallowErrorsPlugin;postgraphile/presets/v4:帮助从 PostGraphile V4 迁移到 V5。源码 v4.ts 显示它通过makeV4Preset(options)把 V4 的配置项(simpleCollections、classicIds、ignoreIndexes、disableDefaultMutations、subscriptions、graphqlRoute等)翻译为 V5 等价物,并附带PgV4BehaviorPlugin、PgV4InflectionPlugin、PgV4SmartTagsPlugin等插件,让 V5 的行为更接近 V4(例如把id重命名为rowId的规则反转、nodeId字段名等);postgraphile/presets/relay:面向希望得到纯正 Relay 风格 Schema 的用户。源码 relay.ts 中的PgRelayPlugin通过globalBehavior关闭主键直接暴露(-constraint:resource:update/-constraint:resource:delete),改用nodeId:resource:update/nodeId:resource:delete,让系统尽可能使用 GraphQL 全局对象标识,并把nodeIdFieldName恢复为id;postgraphile/presets/minify:面向 Schema 导出工作流(特别是 serverless)的实验性 preset。源码 minify.ts 表明它由MinifySchemaPlugin与PgRegistryReductionPlugin组成,会破坏性地剥离 Schema 描述/废弃标记和 registry 元数据以减小导出体积。
如前所述,你的graphile.config.ts(或类似文件)本身也定义了一个 preset:
// graphile.config.mjs const preset = { // 继承官方 preset extends: ["postgraphile/presets/amber"], // 添加插件 plugins: [], // 配置项 schema: { defaultBehavior: "-connection +list", }, }; export default preset;注意defaultBehavior是面向最终配置的全局默认值;如果你在编写一个会被用户继续覆盖的 preset,官方建议改用带schema.globalBehavior的插件(字符串会被前置,回调则返回行为字符串数组、通常把你新增的行为放在当前传入行为之前,这样用户的defaultBehavior优先级更高)。仓库文档 behavior.md 给出了完整的FavourListsPlugin示例。
Extension:用插件扩展 Schema 与执行逻辑
PostGraphile 的基本构件是插件:一个简单的 JavaScript 对象,带有名字并可以挂接到 PostGraphile、Grafast、Grafserv 等项目的各个生命周期。插件极其强大——事实上 PostGraphile 几乎全部核心功能都是通过插件实现的,围绕它也形成了丰富的第三方插件生态。
插件工厂:快速达成目标
一些插件(如 inflection 插件)直接编写即可,但涉及扩展和增强 GraphQL Schema 时,官方建议使用插件工厂来抽象样板代码:
extendSchema:添加字段和类型的首选工厂。允许你用 GraphQL SDL 描述类型(如extend type Query { random: Int }),并用 Grafastplan(甚至传统 GraphQL resolver)提供执行逻辑。从 extend-schema 文档 可见其签名:回调接收build对象(含sql、inflection、pgRegistry、pgResources等),返回含typeDefs以及plans/resolvers的对象;官方明确推荐使用plans而非resolvers(V5 中 resolver 不再获得 Graphile Build 相关的第四个参数,lookahead 已被 Grafast查询计划取代)。例如:
import { extendSchema } from "postgraphile/utils"; import { constant } from "postgraphile/grafast"; export const MyPlugin = extendSchema((build) => { return { typeDefs: /* GraphQL */ ` extend type Query { meaningOfLife: Int } `, objects: { Query: { plans: { meaningOfLife() { return constant(42); }, }, }, }, }; });在 plan resolver 里你可以用context().get("userId")读取 GraphQL context、用build.pgResources获取资源、用users.find()/users.get({ id: $userId })获取表示行集合/单行的 step,再用$row.get("column")读取列值;查询数据库之外的任意逻辑则可借助loadOneWithPgClient/loadManyWithPgClient(需要显式传入 executor,通常取自channels.executor或build.pgExecutor/build.input.pgRegistry.pgExecutors.main)。返回 connection 字段时,plan resolver 必须产出connection(...)step,否则会报$connection.getSubplan is not a function。
wrapPlans:增强 PostGraphile 已生成的行为,例如在 mutation 完成后执行某动作,或自动过滤记录集的一部分结果;changeNullability:修正 Schema 中字段的可空性(标记为 nullable 或 non-nullable),详见 change-nullability 文档。
如果这些工厂都不满足需求,你还可以阅读 extending 文档 学习如何编写自己的 Schema 插件。
通用指导:何时用哪种方式
存储数据:优先建表
如果需要存储数据,第一选择通常是表。只要权限正确,添加表会自动在相关位置生成字段;再配合外键约束、唯一/主键约束、额外列、函数、插件等方式获得更多字段。
派生数据:视图、函数还是 Schema 扩展
如果是从已有数据派生数据,你有三个选择:视图、数据库函数、Schema 扩展。总体按个人偏好选择即可,但需注意:
- 视图:不能接受参数,且需要注解才能表现得更像表;仅在确实需要表式行为时使用(例如为底层可变数据库资源构建无版本外观的门面);
- 函数:可以接受参数,但实现不佳时会有显著的性能开销,务必熟悉 SQL 函数的 inlining,并优先用
LANGUAGE sql编写;IF、LOOP等过程式结构应尽量让位于声明式 SQL。函数也不能被INSERT/UPDATE/DELETE,但你可以再暴露执行这些操作的附加函数; - Schema 扩展:比视图和函数更强大,对性能有更强控制(例如强制内联,甚至把计算搬到 JS 而非数据库),但需要熟悉 JS、SQL 与 Grafast规划系统;而且因为它们不在数据库中,只能被 GraphQL 的消费者使用。
函数 vs 插件
对于简单标量(例如把first_name和last_name拼接成fullName),用数据库函数通常更合适。其它情况建议从你最熟悉的方式入手;一旦遇到性能问题,或发现所需功能与当前模式不兼容,就切换到插件——并且这种切换应该能在不破坏 Schema 的前提下完成。注意视图和函数支持本身也是通过插件实现的,所以它们能做的任何事,插件也都能做到。
常见任务速查
添加一个 Query 根字段
- 创建数据库表、视图或物化视图;
- 创建 custom query 函数;
- 使用插件,例如通过
extendSchema。
向表类型添加字段
(表类型指表示数据库表、视图、物化视图甚至复合类型的 GraphQL 类型;部分建议只适用于其中某些子集。)
- 添加列来存储数据;
- 添加外键约束(视图则用
@foreignKey虚拟约束)以建立类型间关系; - 创建 computed column 函数(可返回标量、记录甚至记录集合);
- 使用插件,例如通过
extendSchema。
向函数派生类型添加字段
官方一般不建议在数据库中使用“匿名”类型——函数最好returns setof named_type而不是returns table(...)——但如果你确实用了匿名类型,为其添加字段可以:
- 修改函数以返回更多值;
- 使用插件,例如通过
extendSchema。
添加 Mutation 字段
- 新增表,让 CRUD mutation 来修改其数据;
- 添加 custom mutation 函数;
- 使用插件,例如通过
extendSchema。
重命名字段
重命名任意字段有三条路:重命名其对应的数据库对象、用@namesmart tag 注解、或使用 inflection 插件。
移除字段
一般来说,预防字段被添加比添加后再移除更好。要避免字段进入 Schema,可以:从数据库删除对应对象、撤销其上的权限,或添加@behavior -*smart tag(也可以只移除特定行为,例如@behavior -update)。如果这些都不奏效,还可以从 Schema 中显式删除字段。
添加文档
- 数据库表、视图、物化视图、约束等可以用
COMMENTSQL 命令添加注释。注意:每次对同一实体执行该命令都会覆盖之前的注释,需小心操作; - 用 smart tags 系统从文件或插件加载描述,应用到数据库派生的 GraphQL 实体上;
- 用插件设置描述:大多数实体都有
description字段,可以通过 graphile-build 插件钩子覆盖——当你需要跨 Schema 设置大量相似描述时,这种方式尤其有用。
小结:一条可落地的定制路线
综合以上内容,PostGraphile V5 的定制思路可以总结为“数据库优先、注解其次、配置兜底、插件兜顶”:
- 先把数据库对象设计好(主键、唯一/外键约束、索引、视图与函数),让 PostGraphile 自动推导出尽可能接近目标的 Schema;
- 用 smart comments /
postgraphile.tags.json5做细粒度注解(重命名、隐藏、虚拟约束、行为覆盖); - 在
graphile.config.ts中通过 Preset 组合全局行为(选择amber底座,按需叠加v4、relay、minify预设); - 遇到数据库与注解都搞不定或性能不佳的场景,用
extendSchema、wrapPlans、changeNullability等插件工厂在 Schema 层精确控制,实现从简单标量到复杂事务 mutation 的任意扩展。
这套组合拳覆盖了从数据库建模到 GraphQL 暴露的完整链路,也是官方文档与仓库源码(presets、smart-tags、behavior、extend-schema)共同描绘的标准实践。
- 后端
- API网关
【免费下载链接】crystal
🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
相关推荐
PostGraphile 定制化总览:从数据库对象、Smart Tags 到 Presets 与插件扩展
PostGraphile 定制化总览:从数据库对象、Smart Tags 到 Presets 与插件扩展 PostGraphile(v5,即 Grafast 时
后端API网关PostGraphile Smart Tags 完全指南:用数据库注释与 JSON5 配置定制 GraphQL Schema
PostGraphile Smart Tags 完全指南:用数据库注释与 JSON5 配置定制 GraphQL Schema 本文基于 PostGraphile
后端API网关PostGraphile Smart Tags 完全指南:不修改数据库即可深度定制 GraphQL Schema
PostGraphile Smart Tags 完全指南:不修改数据库即可深度定制 GraphQL Schema 本篇指南围绕 PostGraphile 的 S
后端API网关
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考