2. GraphQL类型系统与Schema设计
GraphQL 的核心竞争力并不在“查询语法”,而在于它有一套强类型、可自描述的类型系统。服务端用 Schema 定义“数据长什么样、能查什么、能改什么”,客户端据此获得自动补全、字段校验和文档。可以说,Schema 就是前后端之间的一份可执行契约。本节围绕标量类型、对象类型、枚举、接口、联合类型、输入类型、非空与列表修饰符,以及 Schema 定义语言(SDL)展开,帮助你把业务模型翻译成一份结构清晰、约束准确的 Schema。
一、标量类型:数据的最小单元
标量是 Schema 的叶子节点,代表不可再分的具体值。GraphQL 内置五个标量:Int(32 位有符号整数)、Float(双精度浮点)、String(UTF-8 字符串)、Boolean(true/false)、ID(唯一标识,序列化为字符串)。它们决定了字段的取值边界与序列化方式。
内置标量往往不够表达业务语义,例如时间、金额、URL。此时应自定义标量,并在解析器中实现序列化与校验逻辑。例如定义一个 DateTime 标量,约定使用 ISO-8601 字符串:
scalar DateTime
type Article {
id: ID!
title: String!
publishedAt: DateTime
}
自定义标量的价值在于:把“格式约定”从口头文档变成类型约束,客户端能明确知道该字段返回的是可解析的时间字符串,而不是任意文本。
二、对象类型与字段:业务模型的骨架
对象类型由若干具名字段组成,每个字段都有自己的类型。字段类型可以是标量、枚举、其他对象、接口或联合类型。对象类型之间通过字段引用形成图结构,这正是 GraphQL 能按需取数的原因。
type User {
id: ID!
name: String!
email: String
articles: [Article!]!
}
type Article {
id: ID!
title: String!
author: User!
tags: [String!]
}
注意 User.articles 返回 [Article!]!:列表本身非空,且列表内元素也非空。这种“双层非空”是 GraphQL 中非常常见的表达,能有效减少客户端对 null 的防御性判断。
三、枚举、接口与联合类型
枚举(Enum)用于限定字段只能取一组预定义值,例如文章状态:
enum ArticleStatus {
DRAFT
PUBLISHED
ARCHIVED
}
枚举比字符串更安全:拼写错误会在 Schema 校验阶段暴露,而不是等到运行时。
接口(Interface)定义一组字段契约,多个对象类型可以实现它。查询接口类型时,客户端可用内联片段或具名片段按具体类型取字段:
interface Node {
id: ID!
}
type Article implements Node {
id: ID!
title: String!
}
type Video implements Node {
id: ID!
duration: Int!
}
联合类型(Union)表示“结果可能是若干类型之一”,但不要求它们共享字段。它适合搜索结果这类异构集合:
union SearchResult = Article | Video | User
type Query {
search(keyword: String!): [SearchResult!]!
}
接口强调“共同字段”,联合强调“可能类型”,二者都依赖片段来按类型取数,区别在于接口本身可定义字段,联合不能。
四、输入类型:写操作的参数载体
查询参数可以是标量,但变更(Mutation)往往需要一组结构化参数。GraphQL 规定:输入位置只能使用输入类型(Input Object),不能直接使用对象类型。输入类型只包含标量和输入类型字段,不能引用对象类型。
input CreateArticleInput {
title: String!
content: String!
tags: [String!]
status: ArticleStatus = DRAFT
}
type Mutation {
createArticle(input: CreateArticleInput!): Article!
}
把参数收拢为一个 input 对象,好处是字段可增可减、默认值集中、客户端调用形式稳定。示例中 status 默认 DRAFT,调用方不传即按草稿创建。
五、非空与列表修饰符
修饰符是 Schema 中极易出错的部分,需要牢记两条规则:! 表示非空,[] 表示列表,二者可组合出四种语义:
| 写法 | 含义 |
|---|---|
[String] | 列表可为 null,元素也可为 null |
[String!] | 列表可为 null,元素不可为 null |
[String]! | 列表不可为 null,元素可为 null |
[String!]! | 列表与元素都不可为 null |
设计时应遵循“能非空就非空”的原则,但也要谨慎:一旦字段声明为非空,解析器返回 null 就会触发错误并向上冒泡,可能使整个响应字段变为 null。因此对确实可能缺失的数据(如未填写邮箱),应保留可空。
六、SDL:把类型写成可读文本
Schema 定义语言(SDL)是描述 Schema 的文本格式,也是本节所有示例采用的写法。它支持类型定义、字段、参数、默认值、描述文档和指令。描述文档用三引号字符串书写,会出现在 GraphQL 文档和 IDE 提示中:
"""
文章状态
"""
enum ArticleStatus {
DRAFT
PUBLISHED
}
type Article {
"文章标题"
title: String!
status: ArticleStatus!
}
下面这张图把本节的核心类型关系组织在一起,便于对照理解:
七、设计实践要点
第一,命名要一致。类型用大驼峰,字段用小驼峰,枚举值用全大写,这是社区通行约定。第二,输入与输出分离:对象类型用于输出,输入类型用于参数,不要试图复用。第三,非空要克制:对“业务上必然存在”的字段加 !,对“可能缺失”的字段保持可空,避免因一个 null 导致整段响应失败。第四,善用描述文档,把字段含义写进 SDL,让文档随 Schema 自动生成。第五,接口与联合按需选择:需要共享字段用接口,需要表达异构集合用联合。