- 后端
- GraphQL
- API设计
【免费下载链接】type-graphql
Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
TypeGraphQL 是基于graphql-js之上的一层 TypeScript 抽象,它用类和装饰器极大地提升了开发体验(授权、校验、中间件开箱即用),但抽象也带来了运行时开销。本文基于官方性能文档,结合仓库源码与基准测试,系统讲解 TypeGraphQL 的性能开销来源、基准测试数据、异步执行路径的代价,以及simple/simpleResolvers这两个内建性能优化开关的用法、原理与适用边界,帮助你在保持开发效率的同时把查询执行成本降到接近裸写graphql-js的水平。
基准测试:抽象层的运行时开销从何而来
为了量化 TypeGraphQL 抽象层的开销,仓库在 benchmarks 目录下提供了多组对照基准:同样的 Schema 分别用 TypeGraphQL 装饰器方式和"裸写"的graphql-js方式实现,然后在同一台机器、同样 25 000 条数组数据下执行同样的查询并统计耗时。
基准代码位于 benchmarks/array,运行入口 benchmarks/array/run.ts 展示了测试的核心手法:通过ARRAY_ITEMS = 25000控制数据量,用graphql的execute执行一段嵌套查询(顶层数组 +nestedField内层对象),连续跑 50 次(BENCHMARK_ITERATIONS = 50)后输出耗时,并对结果做断言校验,保证比较的是正确结果下的真实耗时。
对照实现分两组:
- 裸
graphql-js:standard.ts 用GraphQLObjectType手工构造对象类型,字段不写resolve(即默认取source上的同名属性); - TypeGraphQL:standard.ts 用
@ObjectType()+@Field()声明同样的SampleObject,并在@Query中返回同样的数组。
官方文档给出的典型实测数据如下(25 000 条数组项):
| 25 000 array items | Deeply nested object | |
|---|---|---|
| Standard TypeGraphQL | 1253.28 ms | 45.57 μs |
graphql-js | 265.52 ms | 24.22 μs |
可以看到,在最苛刻的"返回 25 000 个嵌套对象"场景下,标准 TypeGraphQL 的执行时间大约慢了 5 倍。需要说明的是:
- 这是一组极端压力用例,专门放大抽象层的固定开销;
- 真实应用中,若查询耗时主要来自数据库查询等 I/O,TypeGraphQL 与裸
graphql-js的差距占比会显著缩小,但依然不可忽略。
仓库里的历史运行结果 benchmarks/array/results.txt 记录了该基准在 Core i7 2700K / Windows 10 / Node.js v13.5 环境下的实测数值,可作参考(不同硬件与 Node 版本下绝对值会变化,但相对趋势一致):标准 TypeGraphQL 约 15.5s(50 次合计),裸graphql-js约 13.3s,而带全局中间件的 TypeGraphQL 高达 62.7s,使用simpleResolvers后回落到 15.0s。
性能开销的两个主要来源
1. 默认字段解析器会组装并执行中间件栈
在源码 src/schema/schema-generator.ts 中,每个 Object Type 字段默认都会被赋上一个由createBasicFieldResolver生成的解析器。查看 src/resolvers/create.ts 的实现可以看到,即使一个字段只是"返回 root 上的同名属性",它依然会:
- 把
globalMiddlewares与该字段自身的中间件拼接成一个中间件数组; - 若配置了
authChecker且字段带有roles(如@Authorized),则通过 applyAuthChecker 在栈首插入授权中间件; - 通过 applyMiddlewares 以递归
dispatchHandler的方式逐个执行中间件后再调用真正取值的 handler。
applyMiddlewares的dispatchHandler是async函数,每一层await handlerFn(...)都会引入 Promise 链。因此,只要字段走默认解析器,无论中间件列表是否为空,都可能落入异步执行路径(空栈时会直接同步返回 handler 结果,见 src/resolvers/helpers.ts)。更值得注意的是:文档明确提示,一旦注册了全局中间件,每个隐式字段解析器都会创建中间件栈——这正是"带全局中间件"的基准数据暴涨到 1253.28 ms 的原因。
2. Promise 与 async 字段解析器本身代价高昂
文档给出了裸graphql-js下同步与异步字段解析器的对比:
graphql-js | 25 000 array items |
|---|---|
| sync resolvers | 265.52 ms |
| async resolvers | 512.61 ms |
同样是裸graphql-js,仅把字段resolve改成async(见 async.ts,对应同步版 standard.ts),执行时间就翻了一倍。因此 TypeGraphQL 的优化策略很直接:尽可能避开异步执行路径。
文档列出的条件是:解析器不使用 auth 特性、不使用 args(或已关闭参数校验)、且不返回 Promise 时,TypeGraphQL 可以走更快的同步路径。对应的对照实现可见 async-field-resolvers.ts(所有字段都用@FieldResolver+async包装,实测明显变慢)与 sync-field-resolvers.ts。所以在排查性能瓶颈时,建议先从解析器本身入手:去掉不必要的async/await、关闭未使用的特性、精简参数定义与校验配置,往往立竿见影。
内建优化开关:simple 与 simpleResolvers
如果查询返回的是海量 JSON 形态的数据,且字段不需要字段级访问控制或自定义中间件,就可以用装饰器选项直接整段剥离授权与中间件栈,让字段解析器变成最轻量的形态。
单字段级别:@Field({ simple: true })
只对某个字段生效:
@ObjectType() class SampleObject { @Field() sampleField: string; @Field({ simple: true }) publicFrequentlyQueriedField: SomeType; }该选项在 src/decorators/Field.ts 中定义为FieldOptions.simple,其注释原文即"Set totrueto disable auth and all middlewares stack for this field resolver"。
对象类型级别:@ObjectType({ simpleResolvers: true })
对该 Object Type 的全部字段生效:
@ObjectType({ simpleResolvers: true }) class Post { @Field() title: string; @Field() createdAt: Date; @Field() isPublished: boolean; }该选项在 src/decorators/ObjectType.ts 中定义为ObjectTypeOptions.simpleResolvers,注释为"disable auth and all middlewares stack for all this Object Type fields resolvers",元数据落在ClassMetadata.simpleResolvers(见 src/metadata/definitions/class-metadata.ts)。
两者的合并与覆盖规则
从源码 src/schema/schema-generator.ts 可以看出,两者的优先级是字段级选项优先于类级选项:
const isSimpleResolver = field.simple !== undefined ? field.simple === true : objectType.simpleResolvers !== undefined ? objectType.simpleResolvers === true : false;也就是说,可以用@Field({ simple: false })在simpleResolvers: true的类上为个别字段"恢复"完整的中间件/授权栈。这一行为在测试 tests/functional/simple-resolvers.ts 中有专门验证:测试声明了NormalObject、ObjectWithSimpleField、SimpleObject、SimpleObjectWithNormalField(后者的normalField显式写simple: false),配合全局测试中间件断言了中间件执行次数——普通对象字段执行 2 次(Query + 字段),simple 字段/对象只执行 1 次,simple: false覆盖后恢复为 2 次。
底层效果:解析器退化为"直接取值"
当isSimpleResolver为 true 时,schema-generator 直接不给字段赋解析器(resolve: undefined),完全交给graphql-js默认的取值逻辑(读取source上同名属性),见 src/schema/schema-generator.ts。这相当于绕过了createBasicFieldResolver里的中间件拼接、applyAuthChecker和applyMiddlewares,把字段执行压缩到与裸graphql-js相同的路径上。
实测收益:最高提速 76%,开销降到约 13%
文档给出的最终基准对比(25 000 条数组项):
| 25 000 array items | |
|---|---|
graphql-js | 265.52 ms |
| Standard TypeGraphQL | 310.36 ms |
| TypeGraphQL with a global middleware | 1253.28 ms |
| TypeGraphQL with "simpleResolvers" applied (and a global middleware) | 299.61 ms |
两处关键结论:
- 提速幅度:与"带全局中间件"的 1253.28 ms 相比,加上
simpleResolvers后降至 299.61 ms,提速约 76%; - 接近裸写水平:299.61 ms 相比裸
graphql-js的 265.52 ms 只多约 13% 的开销,远低于文档开头提到的约 500%(5 倍)差距。
对应的 TypeGraphQL 基准实现见 simple-resolvers.ts(@ObjectType({ simpleResolvers: true })+ 仍然挂着一个loggingMiddleware全局中间件),它和 with-global-middleware.ts 的唯一差别就是simpleResolvers这一行,性能差异因此而来。
注意:该优化默认不开启(
simpleResolvers未定义时按false处理,见上文的isSimpleResolver判断),主要就是因为全局中间件与授权特性依赖默认的完整解析器路径。
使用 simpleResolvers 的代价与适用边界
打开simpleResolvers(或字段级simple: true)等于对相关字段关闭两样东西:
@Authorized守卫失效:字段上的授权检查不再执行,这些字段会变成公开可访问;- 全局中间件不再执行:例如性能指标采集、访问日志、错误上报等依赖全局中间件的能力,在该字段上会静默失效。
因此文档给出的建议是:
- 只在确有必要时使用,典型场景就是返回海量嵌套对象的查询(如一次性返回上万元组的列表页);
- 使用前先确认该对象类型的字段确实不需要字段级权限控制,也不依赖任何中间件提供的横切能力;
- 若只有一个高频字段受影响,优先用字段级
@Field({ simple: true })而不是类级simpleResolvers: true,把影响面降到最小; - 若个别字段仍需授权/中间件,用
@Field({ simple: false })显式恢复(源码支持,测试已验证)。
小结:性能调优路线图
结合本文可以整理出一条务实的调优路径:
- 先用基准与真实负载确认瓶颈确实在字段解析层(而不是数据库或网络);
- 检查并精简解析器:移除不必要的
async/await、关闭未使用的 auth/args 校验等特性; - 审视全局中间件的使用范围——它会让每个隐式字段都背上中间件栈;
- 对返回海量数据的对象类型启用
simpleResolvers,或用字段级simple: true精准优化高频字段; - 注意授权与中间件的失效边界,必要时用
simple: false局部恢复,并在 tests/functional/simple-resolvers.ts 的模式上补充自己的回归测试。
通过这套组合拳,TypeGraphQL 应用的查询执行开销可以从"5 倍于裸graphql-js"降低到"仅约 13% 的附加开销",在享受装饰器开发体验的同时,把运行时成本压到接近手写graphql-js的水平。
- 后端
- GraphQL
- API设计
【免费下载链接】type-graphql
Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
相关推荐
TypeGraphQL 性能优化实战指南:从基准测试到 simpleResolvers 加速
TypeGraphQL 性能优化实战指南:从基准测试到 simpleResolvers 加速 TypeGraphQL 是构建在 graphql js 之上的 T
后端GraphQLAPI设计TypeGraphQL 性能深度剖析:基准测试、异步执行路径与 simpleResolvers 优化实战
TypeGraphQL 性能深度剖析:基准测试、异步执行路径与 simpleResolvers 优化实战 TypeGraphQL 本质上是构建在 GraphQL
后端GraphQLAPI设计STL-Thumbnail:Windows资源管理器3D模型预览终极解决方案
STL Thumbnail:Windows资源管理器3D模型预览终极解决方案 您是否曾经在管理大量STL文件时感到困扰?每次都需要打开专业的3D软件才能查看模型
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考