不喜欢广告? 无广告 今天

GraphQL 模式 无需样板代码即可编写、格式化和理解 SDL

更新于

GraphQL 模式是服务器和客户端之间的自文档化协议。本指南将从零开始介绍模式定义语言(SDL),涵盖类型、标量、查询、变更、输入类型、枚举和接口,使您能够自信地阅读和编写模式。

GraphQL 模式:无需样板代码即可编写、格式化和理解 SDL 1
广告 移除?

如果你曾使用过REST API,然后第一次接触GraphQL模式,语法可能看起来熟悉但无法解析。这是正常的。模式定义语言(SDL)简洁,但每行都承载着大量含义。本文将详细解释,让你无需每五分钟就查阅文档,就能阅读和编写模式。

GraphQL模式实际上是什么

GraphQL模式是你的API服务器与每个调用它的客户端之间的契约。它精确地定义了数据的存在、可用的操作以及响应的结构。与REST不同——在REST中,你通过文档、试错来发现端点——GraphQL让契约明确且可被机器读取。

每个GraphQL API都从一个用SDL编写的模式开始。服务器在运行时会验证所有查询与该模式的一致性。如果客户端请求了一个在模式中不存在的字段,请求将在任何解析器运行之前被拒绝。这是GraphQL类型系统的核心价值:模式是API表面范围的唯一真实来源。

SDL基础:类型、查询和变更

每个SDL文件都是由类型定义构建而成。以下是最小的可用模式示例:

type Query {
  hello: String
}

Query 是读取操作的入口点。每个模式都必须包含一个。字段 hello 返回一个 String ——五种内置标量类型之一。

五种内置标量类型是:

  • 细绳 ——UTF-8文本
  • Int ——32位有符号整数
  • Float ——双精度浮点数
  • Boolean ——真或假
  • ID ——一个唯一标识符,以字符串形式序列化

实际的模式会定义自定义对象类型。以下是一个更典型的模式示例:

type User {
  id: ID!
  name: String!
  email: String!
  role: UserRole!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  body: String
  author: User!
  publishedAt: DateTime
}

type Query {
  user(id: ID!): User
  posts: [Post!]!
}

这20行中发生了很多事情。让我们逐一解析。

非空字段和列表

! 在类型后表示该字段是非空的——它永远不会返回 null。如果没有这个修饰,该字段是可空的,客户端必须处理缺失值。在运行时,这种区别至关重要:如果一个非空字段的解析器抛出异常或返回null,GraphQL会将null向上传播,可能使父级字段也变为null。

列表使用方括号: [Post] 是一个可空的、包含可空帖子的列表。 [Post!]! 是一个非空的、包含非空帖子的列表。这是你通常想要的版本——一个始终返回数组(可能为空)的字段,且数组中的每个元素始终是真实对象,而不是null。

四种组合的详细说明:

  • [Post] ——可空列表,可空项(很少有用)
  • [Post!] ——可空列表,非空项
  • [Post]! ——非空列表,可空项
  • [Post!]! ——非空列表,非空项(最常见)

变更:写入数据

查询是只读的。变更处理写入操作。类型 Mutation 的结构与 Query相同,但根据约定,其字段具有副作用:

type Mutation {
  createPost(input: CreatePostInput!): Post!
  deletePost(id: ID!): Boolean!
}

注意 CreatePostInput! ——这是一个输入类型,这直接引出了下一个概念。

输入类型与输出类型

这是GraphQL模式中最常见的混淆点之一。你 不能 将一个像 Post 这样的对象类型重新用于变更的参数。SDL之所以有这两种类型,是有原因的:

  • 对象类型 (type) ——用于响应。可以包含解析器和复杂字段逻辑。
  • 输入类型 (input) ——用于参数。纯数据,无解析器。字段只能引用标量或其它输入类型。
input CreatePostInput {
  title: String!
  body: String
  authorId: ID!
}

这种分离保持了参数验证的清晰性,并防止了如果输出类型(可以在复杂图中相互引用)直接用作输入时可能出现的循环引用问题。

自定义标量

五种内置标量类型并不能涵盖所有情况。日期时间字符串、URL、JSON块等需要自定义标量。你可以这样声明它们:

scalar DateTime
scalar JSON
scalar URL

标量声明告诉SDL使用哪个名称。实际的序列化、反序列化和验证逻辑存在于服务器实现中——而不是在模式文件中。像 graphql-scalars 这样的库提供了常见类型的现成实现,因此你无需从头编写。

当一个 String 在技术上是正确的,但在语义上是错误的时,使用自定义标量。一个字段类型为 DateTime 传达了意图;而一个字段类型为 String 迫使客户端猜测格式。

枚举

枚举将字段限制在一组固定的字符串值内。相比于原始字符串,枚举更可靠,因为模式在类型级别强制规定了允许的值:

enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

一个字段类型为 UserRole 永远不会返回意外的字符串。一个字段类型为 String 可能返回任何值。当值集合已知、稳定且对客户端有意义时,请使用枚举——这使得模式探测显著更有用。

接口和联合

当一个字段可以返回不同类型对象时,GraphQL提供了两种机制:接口和联合。

接口 定义一组共享的字段。实现接口的类型必须包含这些字段:

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
}

type Post implements Node {
  id: ID!
  title: String!
}

联合 将类型分组,而无需要求共享字段。当可能的类型在结构上没有共同点时,联合非常有用:

union SearchResult = User | Post | Comment

type Query {
  search(query: String!): [SearchResult!]!
}

客户端使用内联片段来处理联合或接口中的每种类型: ... on User { name }。如果你返回的是多态数据,当类型共享字段时,优先使用接口;当它们不共享字段时,使用联合。

指令

指令在字段或类型级别修改行为。每个GraphQL实现都内置了两个指令:

  • @deprecated(reason: "Use newField instead") ——在探测中标记一个字段为已弃用
  • @skip(if: Boolean)@include(if: Boolean) ——在查询中作为客户端条件使用

你也可以为诸如身份验证守卫、速率限制提示或缓存注解等场景定义自定义指令。它们在模式中声明,并在服务器上实现:

directive @auth(requires: UserRole = ADMIN) on FIELD_DEFINITION

type Mutation {
  deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
}

GraphQL与REST:何时使用哪种

GraphQL的模式优先方法在特定情况下表现优异。当满足以下条件时,它是正确的选择:

  • 多个客户端(网页、移动、第三方)需要相同数据的不同子集
  • 你希望在不进行版本控制的情况下演进API——可以自由添加字段,弃用而非删除
  • 领域天然具有复杂实体之间的关系结构
  • 你希望拥有一个自我文档化的API,客户端可以通过探测来探索它

当满足以下条件时,REST仍然是更好的选择:

  • 你正在构建具有可预测、扁平负载的简单CRUD端点
  • HTTP缓存至关重要——GraphQL的POST请求默认不支持缓存
  • 团队规模小且API表面稳定——REST的开销更小
  • 你遇到了N+1问题,并且不想配置DataLoader来解决它

N+1问题值得暂停考虑。在一个GraphQL解析器中,当它获取 posts 和每个帖子的 author时,一个朴素的实现会为每个帖子发起一次数据库查询来加载作者。对于50个帖子,这将导致一个请求51次查询。REST没有这个问题,因为你可以精确控制每个端点运行的SQL语句。在GraphQL中,你通过批处理(DataLoader或等效方案)来解决它——这是可以解决的,但需要正确配置。

格式化和验证你的SDL

随着团队的增长,模式文件会逐渐积累不一致的缩进、间距不匹配和结构漂移。在提交模式变更前,请运行格式化工具以标准化间距、排序字段,并在进入CI前发现语法错误。

GraphQL Schema Formatter on iotools.cloud 正是这样做的——粘贴你的SDL,获得格式整洁、一致的输出,并在行内显示验证错误。它支持多类型模式、自定义标量、指令以及手动格式化容易出错的边缘情况。作为提交或与另一团队共享模式前的最终检查非常有用。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?