- 后端
- GraphQL
- 代码生成
【免费下载链接】gqlgen
go generate based graphql server library
本文以 gqlgen 仓库中的_examples/type-system-extension示例为主线,讲解 GraphQL 类型系统扩展(Type System Extensions)的完整用法:如何把 schema、type、interface、union、enum、input object、scalar 与字段级别的extend拆分到多个.graphql文件,并结合自定义指令实现统一的日志切面。读完本文,你将掌握用 gqlgen 组织多文件 Schema、使用extend语法增量扩展类型,以及通过DirectiveRoot挂载指令实现的能力。
什么是 Type System Extensions
GraphQL 规范允许在定义类型系统的同时,通过extend关键字对已有类型做“追加式”扩展,而不必修改原始定义。它的典型价值在于:
- 模块化组织:不同团队或模块可以各自维护一份扩展文件,互不干扰;
- 增量演进:在不动基础类型定义的前提下补充字段、指令、操作入口;
- 指令注入:扩展的同时可以在新字段或类型上附加指令,实现横切关注点(日志、鉴权、限流等)。
本示例对应的规范章节是 GraphQL 规范的 Type System Extensions(当前仓库 README 中给出的参考链接),覆盖了schema、type、interface、union、enum、input object、scalar共七种可扩展对象,外加通过extend type为对象增加新字段的场景。
示例整体结构
_examples/type-system-extension目录组织如下:
- gqlgen.yml:生成配置,把
./schemas/*.graphql全部作为 Schema 输入; - schemas/:一个基础 Schema 文件加八个扩展文件;
- generated.go、models_gen.go:由
go generate生成的可执行 Schema 与模型; - directive.go:六个日志指令的实现;
- resolver.go:根 Resolver 与查询/变更实现;
- server/server.go:HTTP 服务入口。
其中基础 Schema schemas/schema.graphql 定义了核心骨架:
schema { query: MyQuery } interface Node { id: ID! } type Todo implements Node { id: ID! text: String! state: State! } type MyQuery { todos: [Todo!]! } union Data = Todo enum State { NOT_YET DONE }注意:这里并没有定义MyMutation,也没有Todo.verified字段——它们全部由后续的扩展文件补充。
运行示例
在仓库根目录执行:
$ go run ./_examples/type-system-extension/server/server.go 2018/10/25 12:46:45 connect to http://localhost:8080/ for GraphQL playground启动后打开http://localhost:8080/即可进入 GraphQL playground。用 curl 验证查询:
$ curl -X POST 'http://localhost:8080/query' --data-binary '{"query":"{ todos { id text state verified } }"}' {"data":{"todos":[{"id":"Todo:1","text":"Buy a cat food","state":"NOT_YET","verified":false},{"id":"Todo:2","text":"Check cat water","state":"DONE","verified":true},{"id":"Todo:3","text":"Check cat meal","state":"DONE","verified":true}]}}verified字段来自扩展文件,这证明扩展后的 Schema 已被 gqlgen 正确合并并生成可执行代码。服务入口 server/server.go 支持通过环境变量PORT覆盖默认端口8080,并同时注册了GET与POST两种 transport。
逐类拆解:七类扩展文件的写法
1. schema 扩展:补充 Mutation 入口与查询字段
schemas/schema-extension.graphql 演示了最常用的一类扩展——给根类型追加操作入口:
extend schema { mutation: MyMutation } extend type MyQuery { todo(id: ID!): Todo } type MyMutation { createTodo(todo: TodoInput!): Todo! } input TodoInput { text: String! }extend schema { mutation: MyMutation }在原有只有query的 Schema 上新增mutation根操作;extend type MyQuery为查询根类型追加todo(id: ID!)字段;- 新增的
MyMutation与TodoInput虽然是普通类型定义,但通过上面的extend与根类型连接,构成了完整的变更能力。
对应的实现位于 resolver.go:NewRootResolver()返回包含三条 Todo 数据的ResolverRoot,MyQuery()与MyMutation()分别返回queryResolver与mutationResolver;CreateTodo会生成形如Todo:4的新 ID 并追加到内存切片中。
2. type 扩展:为对象追加字段
schemas/type-extension.graphql 为Todo追加了布尔字段verified,并挂上字段级指令:
directive @fieldLogging on FIELD_DEFINITION extend type Todo { verified: Boolean! @fieldLogging }由于verified是扩展字段,gqlgen 会在模型生成时把它并入Todo结构体。从 resolver.go 可以看到,内存中的三条 Todo 都直接给定了Verified值,而模型文件 models_gen.go 中Todo结构体同时包含State、Text、Verified等字段。
3. interface 扩展
schemas/interface-extension.graphql:
directive @interfaceLogging on INTERFACE extend interface Node @interfaceLogging该扩展不新增成员,只给接口挂上@interfaceLogging指令,用于演示接口级别指令的声明与合并。
4. union 扩展
schemas/union-extension.graphql:
directive @unionLogging on UNION extend union Data @unionLogging同样地,仅为Data联合类型附加指令。
5. enum 扩展
schemas/enum-extension.graphql:
directive @enumLogging on ENUM extend enum State @enumLogging枚举值NOT_YET、DONE本身没有变化,扩展只为State附加指令。
6. input object 扩展
schemas/input-object-extension.graphql:
directive @inputLogging on INPUT_OBJECT extend input TodoInput @inputLoggingTodoInput定义在 schema-extension 文件中,这里通过extend input为它补充指令。生成代码中,反序列化输入对象时会先检查ec.Directives.InputLogging是否实现,未实现则返回 “directive inputLogging is not implemented” 错误(见 generated.go 中unmarshalInputTodoInput相关逻辑)。
7. scalar 扩展
schemas/scalar-extension.graphql:
directive @scalarLogging on SCALAR extend scalar ID @scalarLoggingID是 GraphQL 内置标量,通过extend scalar同样可以为其附加指令——这是对内置类型进行扩展的典型例子。
指令驱动:一个统一的日志切面
上述七个文件里定义了六个指令(enumLogging、fieldLogging、inputLogging、interfaceLogging、objectLogging、scalarLogging与unionLogging),它们的实现都集中在 directive.go。以FieldLogging为例:
func FieldLogging(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) { rc := graphql.GetFieldContext(ctx) log.Printf("field logging: %v, %s, %T, %+v", rc.Path(), rc.Field.Name, obj, obj) return next(ctx) }要点:
- 指令函数签名统一为
func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error),next是原始解析器,调用next(ctx)继续执行; - 通过
graphql.GetFieldContext(ctx)可取得当前字段上下文,包括rc.Path()(字段路径)、rc.Field.Name(字段名)以及父对象obj; - 每个指令都遵循“先打日志、再放行”的包装器模式,因此查询时会按字段执行顺序输出多条日志,便于追踪解析链路。
指令的注册发生在 server/server.go:
srv := handler.New( extension.NewExecutableSchema( extension.Config{ Resolvers: extension.NewRootResolver(), Directives: extension.DirectiveRoot{ EnumLogging: extension.EnumLogging, FieldLogging: extension.FieldLogging, InputLogging: extension.InputLogging, ObjectLogging: extension.ObjectLogging, ScalarLogging: extension.ScalarLogging, UnionLogging: extension.UnionLogging, }, }, ), )从 generated.go 的DirectiveRoot结构体可以看到,gqlgen 为 Schema 中每个指令生成一个对应的函数字段;若某个指令未在DirectiveRoot中赋值,运行时会在对应位置直接返回 “directive xxx is not implemented” 错误。因此,凡是 Schema 中声明过的指令,都必须在这里提供实现,这是 gqlgen 使用指令的一条硬性要求。
生成配置与重新生成
gqlgen.yml 内容如下:
schema: - ./schemas/*.graphql exec: filename: generated.go model: filename: models_gen.go关键点:
schema使用 glob 模式./schemas/*.graphql,把基础文件与所有扩展文件一次性纳入合并范围——这正是多文件组织 Schema 的入口;exec.filename指定生成的可执行 Schema(含解析器调度、指令调用点)输出到generated.go;model.filename指定生成的 Go 模型输出到models_gen.go。
重新生成代码的入口是 resolver.go 首行的//go:generate指令:
//go:generate go run ../../testdata/gqlgen.go在_examples/type-system-extension目录下执行go generate ./...,gqlgen 会重新读取全部 Schema 文件并生成generated.go与models_gen.go。修改任何schemas/*.graphql文件后都需要重新生成,扩展字段(如verified)才会反映到模型与执行代码中。
扩展后的完整能力验证
将基础 Schema 与所有扩展合并后,最终得到的类型系统包含:
- 查询:
todos: [Todo!]!(基础定义)、todo(id: ID!): Todo(扩展定义); - 变更:
createTodo(todo: TodoInput!): Todo!(扩展定义); - 对象:
Todo在基础字段id、text、state之外,获得扩展字段verified: Boolean!; - 接口 / 联合 / 枚举 / 输入对象 / 标量:均保留原始定义,并通过
extend附加了各自的日志指令。
启动服务后,除了本文开头演示的查询,还可以调用扩展出的变更操作:
$ curl -X POST 'http://localhost:8080/query' \ --data-binary '{"query":"mutation { createTodo(todo: { text: \"Buy a litter box\" }) { id text state verified } }"}'响应会返回形如{"data":{"createTodo":{"id":"Todo:4","text":"Buy a litter box","state":"NOT_YET","verified":false}}}的结果,同时服务端日志会依次打印input object logging、object logging、field logging等记录,直观展示指令切面在完整执行链路上的生效顺序。
小结
_examples/type-system-extension用一份基础 Schema 加八份扩展文件,完整演示了 GraphQL Type System Extensions 在 gqlgen 中的落地方式:extend schema补齐根操作,extend type追加字段,extend interface / union / enum / input / scalar为各类类型附加指令。结合DirectiveRoot统一注册指令实现,可以在不修改原始类型定义的前提下,为 Schema 注入日志等横切能力。这一模式非常适合需要多人协作、按模块拆分 Schema,或希望保持核心类型稳定、通过增量扩展演进接口的 gqlgen 项目。
延伸阅读:示例配套的测试与更多配置可参考仓库中的 getting-started 与 config 文档;指令定义与生成的完整细节可继续阅读本示例的 generated.go。
- 后端
- GraphQL
- 代码生成
【免费下载链接】gqlgen
go generate based graphql server library
相关推荐
Relay Client Schema Extensions 完全指南:用本地状态扩展 GraphQL Schema 的增删改查实战
Relay Client Schema Extensions 完全指南:用本地状态扩展 GraphQL Schema 的增删改查实战 Relay 不仅用于读写服
前端开发工具gs-quant 从零到一:卡尔曼滤波价差套利实战
gs quant 从零到一:卡尔曼滤波价差套利实战 2023 03 07,铁矿石单日暴涨 4%,螺纹钢 铁矿石的固定布林带价差策略直接打穿止损。把均值换成动态估
后端数据库文档数据库DialoGPT-medium-joshua-openmind完整教程:从模型下载到生成对话的简单步骤
DialoGPT medium joshua openmind完整教程:从模型下载到生成对话的简单步骤 DialoGPT medium joshua openm
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考