跳转到内容

gqlgen — Go 用 schema 先写好再让编译器生成 GraphQL server

是什么

gqlgen 是一个 Go 语言 GraphQL 服务器框架。日常类比:像让裁缝先看你画的衣服图纸(schema),再去裁布料缝衣服(生成代码)——而不是裁完一堆通用布条让你运行时拼。

GraphQL 是 Facebook 2015 开源的查询语言,前端可以一次性问 “给我用户名 + 他的所有帖子标题”,后端根据查询返回正好这些字段。Go 写 GraphQL server 历来有两条路:用 反射(graphql-go)每次请求时去字符串匹配字段名,慢且容易在运行时崩;或者全手写 type assertion,啰嗦到让人放弃。

gqlgen 走第三条路:先写 .graphqls schema,跑一次 go generate,工具读 schema 生成一堆 Go struct 和 resolver 接口骨架。你只需要实现这些接口方法。编译器从此就能在编译期发现”你 schema 改了字段但 resolver 没跟上”,运行时几乎零反射。

为什么重要

不理解 gqlgen 的 codegen 套路,下面这些事都没法解释:

  • 为什么 Go 圈推 GraphQL 服务时几乎只剩 gqlgen 这一个名字(其它老库都不维护了)
  • 为什么 schema-first 比 code-first(用 Go struct 反推 schema)更受 Go 社区欢迎
  • 为什么 N+1 查询是用 GraphQL 必踩的坑,不论用什么语言都要配 dataloader
  • 为什么 gRPC 用户切到 GraphQL 时会很自然——两者都是 schema 驱动 + codegen 流派

核心要点

gqlgen 的工作流可以拆成 三步

  1. 写 schema:用 GraphQL SDL 写 .graphqls 文件,定义类型 / 查询 / 突变。类比:先画建筑图纸,告诉施工队每面墙在哪。

  2. 跑 codegengo run github.com/99designs/gqlgen generate,工具读 schema + gqlgen.yml 配置,生成 generated.go(一万行类型安全胶水)+ resolver.go(空方法骨架)。类比:图纸进 CNC 机床,自动切出所有零件。

  3. 填 resolver:你打开 resolver.go,每个方法填业务逻辑(查数据库、调外部 API)。schema 和 resolver 类型完全对齐,少一个字段、参数错一个类型,编译都过不了

整套理念叫 schema-first + code generation,对立面是 nestjs/graphql 那种”用装饰器写 Go struct 反推 schema”的 code-first。

实践案例

案例 1:最小 hello world

写一个 schema.graphqls

type Query {
hello(name: String!): String!
}

go run github.com/99designs/gqlgen init,生成 resolver.go 骨架。打开它实现:

func (r *queryResolver) Hello(ctx context.Context, name string) (string, error) {
return "Hello, " + name, nil
}

启动 server,浏览器开 localhost:8080,输 { hello(name: "Jason") },返回 {"data":{"hello":"Hello, Jason"}}整个过程你没碰任何反射 / map[string]interface{}

案例 2:嵌套字段 + N+1 陷阱

schema 加上:

type User { id: ID!, friends: [User!]! }
type Query { users: [User!]! }

resolver 朴素写法:

func (r *userResolver) Friends(ctx context.Context, u *User) ([]*User, error) {
return db.QueryFriends(u.ID) // 每个 user 触发一次 SQL
}

查 100 个用户,会发 101 次 SQL(1 次查 users + 100 次查 friends)。修复用 dataloader:把同一 tick 内的 ID 攒一批,一次 WHERE id IN (...) 批量查。这是 GraphQL 必备配套。

案例 3:自定义 scalar UUID

GraphQL 内置只有 Int / Float / String / Boolean / ID。要塞 UUID,在 gqlgen.yml

models:
UUID:
model: github.com/google/uuid.UUID

然后写 MarshalGQL / UnmarshalGQL 两个方法告诉 gqlgen 怎么把 UUID 序列化进 JSON。新人常偷懒直接用 String,结果 schema 里看不出哪些字段必须是 UUID 格式,类型安全的好处折一半

踩过的坑

  1. N+1 查询:嵌套 resolver 默认逐条触发,查列表必踩,必须接 dataloader 批量预取,否则数据库直接被打爆。

  2. 改 schema 忘跑 generate:手改 .graphqls 后没跑 go generate,老的 generated.go 还在,编译居然过——但运行时字段对不上。把 generate 加到 CI 是唯一保险。

  3. 自定义 scalar 没配:UUID / Time 这种业务常用类型直接用 String 凑合,schema 看不出格式约束,前端拿到一坨字符串自己解析,类型安全名存实亡。

  4. generated.go 太大:大型项目 schema 一两千行,generated.go 上万行,IDE goimports / gopls 卡顿。可拆成 federation 子图,把单服务 schema 控制在 500 行内。

适用 vs 不适用场景

适用

  • 中大型 Go 后端要对外暴露灵活查询接口(前端 / 移动端按需取字段)
  • 已用 GraphQL 但当前 server 是 graphql-go 反射版,性能瓶颈想升级
  • 团队已习惯 protobuf / OpenAPI 这类 schema-first + codegen 流派
  • 多服务架构想做 GraphQL Federation(Apollo Federation 子图)

不适用

  • 内部 RPC 调用(用 grpc-go 更直接,schema + codegen 但二进制更省)
  • 极简 CRUD(用 REST + fiber / echo / gin 更轻)
  • 团队不愿意把 codegen 加到 build pipeline(schema-first 的硬门槛)
  • Node.js 技术栈(直接用 apollo-server / graphql-yoga,生态更大)

历史小故事(可跳过)

  • 2012 年:Facebook 内部开发 GraphQL,解决 News Feed 在弱网下”取太多 / 太少字段”的问题。
  • 2015 年:GraphQL spec 开源;Go 圈 neelance 写出 graphql-go,用反射实现。
  • 2018 年:99designs(设计众包公司)把内部用的 codegen 版本开源,命名 gqlgen,schema-first + 生成代码两大特点直击 graphql-go 痛点。
  • 2020 年:加入 GraphQL Foundation 孵化项目,成为 Go 圈事实标准。
  • 2023 年:支持 Apollo Federation v2,多服务 GraphQL 架构在 Go 圈站稳。

之后社区其它 Go GraphQL 库基本停滞,gqlgen 一家独大。

学到什么

  1. schema-first + codegen 是 GraphQL / gRPC / OpenAPI 这类强 schema 协议的共同套路——先定契约,再让工具替你生成胶水
  2. 反射换编译期检查 是性能 + 类型安全双赢;代价是构建链多一步 go generate
  3. N+1 查询是 GraphQL 的原罪,dataloader 是配套必须品,不是可选
  4. 选框架时看”对立面”——gqlgen 与 graphql-go 的对比就能告诉你 codegen 派 vs reflection 派各自的取舍

延伸阅读

关联

  • apollo-server —— Node.js 圈最流行的 GraphQL server,对照 gqlgen 看出 schema-first vs code-first 之争
  • graphql-yoga —— 轻量 Node.js GraphQL server,gqlgen 在 Go 圈的对应
  • grpc-go —— 同流派:schema-first + codegen,但走二进制协议
  • trpc —— 极致简化 schema,TS 端到端类型推导,GraphQL 的另一种解题
  • go-zero —— Go 微服务全家桶,常和 gqlgen 一起做对外 GraphQL 网关
  • fiber —— Go 高性能 HTTP 框架,可作为 gqlgen 的传输层底座
  • prisma —— 数据库层 schema-first + codegen,理念上跟 gqlgen 同源

反向链接

  • apollo-server —— Apollo Server — Node 端 GraphQL 服务端的事实标准
  • echo —— Echo — 极简高性能 Go 框架,5 行起服务
  • fiber —— Fiber — 把 Express 写法搬到 Go 上的高性能 web 框架
  • gin —— Gin — Go 写 web API 的事实标准框架
  • go-zero —— go-zero — 一份契约文件生成整套 Go 微服务
  • graphql-yoga —— GraphQL Yoga — 跨运行时的轻量 GraphQL 服务器
  • grpc-go —— gRPC-Go — Google RPC 框架的官方 Go 实现
  • hot-chocolate —— Hot Chocolate — .NET 里 code-first 写 GraphQL 服务器
  • prisma —— Prisma — 类型安全 ORM
  • trpc —— tRPC — TS 端到端类型安全 RPC