GraphQL 基础概念
GraphQL是一种用于API的查询语言和运行时环境,由Facebook在2015年开源。它提供了一种更高效、更灵活的API设计方式,让客户端能够精确地获取所需数据。
GraphQL核心概念
什么是GraphQL
GraphQL的核心思想是"客户端驱动"——客户端决定需要什么数据,服务器只返回客户端请求的数据。这与传统的REST API形成鲜明对比,REST API通常返回固定的数据结构,客户端可能获取到过多或过少的数据。
GraphQL不是一个数据库,也不是一个框架,而是一种规范。你可以使用任何编程语言实现GraphQL服务器,也可以连接任何数据源(数据库、REST API、微服务等)。
GraphQL解决的问题
过度获取(Over-fetching):REST API返回的数据可能包含客户端不需要的字段。
REST响应:
{
"id": 1,
"name": "张三",
"email": "[email protected]",
"age": 25,
"address": "北京市朝阳区...",
"phone": "13800138000",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-10-21T10:30:00Z"
}
客户端只需要name和email,但收到了所有字段
获取不足(Under-fetching):REST API返回的数据可能不够,需要额外的请求。
场景:获取用户及其文章列表
REST方式:
1. GET /users/123 获取用户信息
2. GET /users/123/articles 获取用户文章
需要两次请求才能获取完整数据
GraphQL解决方案:
query {
user(id: 123) {
name
email
articles {
title
createdAt
}
}
}
一次请求获取精确需要的数据。
GraphQL的特点
- 声明式数据获取:客户端声明需要什么数据,而不是如何获取
- 强类型系统:Schema定义了API的类型结构,提供编译时检查
- 单端点:所有请求都发送到同一个端点
- 自描述:通过内省查询可以获取API的完整结构
- 版本无关:可以通过添加字段来演进API,无需版本控制
Schema与类型系统
Schema是GraphQL的核心,它定义了API的数据结构和操作。GraphQL使用强类型系统,每个字段都有明确的类型。
Schema定义语言(SDL)
GraphQL提供了Schema定义语言(Schema Definition Language,SDL),用于定义类型和操作。
标量类型(Scalar Types)
标量类型是GraphQL中最基本的数据类型,表示单个值。GraphQL内置了以下标量类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| Int | 有符号32位整数 | 42 |
| Float | 有符号双精度浮点数 | 3.14 |
| String | UTF-8字符序列 | "Hello" |
| Boolean | 布尔值 | true |
| ID | 唯一标识符,序列化为字符串 | "123" |
自定义标量类型:
scalar Date
scalar DateTime
scalar JSON
scalar URL
自定义标量需要在代码中实现序列化和验证逻辑。
对象类型(Object Types)
对象类型是GraphQL中最常用的类型,它包含一组字段。
type User {
id: ID!
name: String!
email: String!
age: Int
isActive: Boolean!
createdAt: DateTime!
articles: [Article!]!
}
type Article {
id: ID!
title: String!
content: String!
author: User!
tags: [String!]!
createdAt: DateTime!
updatedAt: DateTime
}
类型定义说明:
!表示非空(Non-Null),该字段必须返回值[Type]表示列表类型[Type!]表示列表元素不能为空[Type!]!表示列表本身和元素都不能为空
枚举类型(Enum Types)
枚举类型限制字段只能取特定的值。
enum ArticleStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum UserRole {
ADMIN
EDITOR
VIEWER
}
type Article {
id: ID!
title: String!
status: ArticleStatus!
author: User!
}
枚举类型的优势:
- 限制输入值,提供验证
- 自文档化,明确可选值
- 在客户端代码中可以生成对应的常量
接口类型(Interface Types)
接口定义了一组公共字段,其他类型可以实现这些接口。
interface Node {
id: ID!
createdAt: DateTime!
}
interface Content {
title: String!
content: String!
author: User!
}
type Article implements Node & Content {
id: ID!
createdAt: DateTime!
title: String!
content: String!
author: User!
tags: [String!]!
}
type Comment implements Node & Content {
id: ID!
createdAt: DateTime!
title: String!
content: String!
author: User!
article: Article!
}
查询接口类型时,需要使用内联片段获取具体类型的字段:
query {
contents {
title
content
author {
name
}
... on Article {
tags
}
... on Comment {
article {
title
}
}
}
}
联合类型(Union Types)
联合类型表示一个字段可以是多种类型之一,但不需要共享字段。
union SearchResult = User | Article | Comment
type Query {
search(keyword: String!): [SearchResult!]!
}
查询联合类型时,必须使用内联片段:
query {
search(keyword: "GraphQL") {
... on User {
id
name
email
}
... on Article {
id
title
createdAt
}
... on Comment {
id
content
}
}
}
输入类型(Input Types)
输入类型用于传递复杂对象作为参数,常用于Mutation。
input CreateUserInput {
name: String!
email: String!
age: Int
role: UserRole = VIEWER
}
input UpdateUserInput {
name: String
email: String
age: Int
}
input ArticleFilterInput {
status: ArticleStatus
authorId: ID
tags: [String!]
createdAfter: DateTime
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
}
输入类型与对象类型的区别:
- 输入类型用于参数,对象类型用于返回值
- 输入类型的字段不能有参数
- 输入类型可以引用其他输入类型
操作类型
GraphQL定义了三种操作类型:Query、Mutation和Subscription。
Query(查询)
Query用于读取数据,类似于REST的GET请求。
type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
article(id: ID!): Article
articles(filter: ArticleFilterInput): [Article!]!
search(keyword: String!): [SearchResult!]!
me: User!
}
Query的特点:
- 只读取数据,不修改状态
- 并行执行多个字段
- 应该是幂等的
Mutation(变更)
Mutation用于修改数据,类似于REST的POST、PUT、DELETE请求。
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
deleteUser(id: ID!): Boolean!
createArticle(input: CreateArticleInput!): Article!
updateArticle(id: ID!, input: UpdateArticleInput!): Article
deleteArticle(id: ID!): Boolean!
publishArticle(id: ID!): Article!
addComment(articleId: ID!, input: CreateCommentInput!): Comment!
}
Mutation的特点:
- 修改服务器状态
- 串行执行(一个Mutation完成后才执行下一个)
- 通常返回修改后的数据
Subscription(订阅)
Subscription用于实时数据推送,基于WebSocket实现。
type Subscription {
onArticleCreated: Article!
onArticleUpdated(id: ID): Article!
onCommentAdded(articleId: ID!): Comment!
onUserStatusChanged(userId: ID!): User!
}
客户端订阅后,当相关事件发生时,服务器会主动推送数据:
subscription {
onCommentAdded(articleId: "123") {
id
content
author {
name
}
createdAt
}
}
参数与分页
字段参数
GraphQL的每个字段都可以有参数。
type Query {
user(id: ID!): User
articles(
limit: Int = 10
offset: Int = 0
status: ArticleStatus
authorId: ID
): [Article!]!
}
type User {
id: ID!
name: String!
articles(limit: Int = 5): [Article!]!
}
查询示例:
query {
user(id: "123") {
name
articles(limit: 3) {
title
}
}
}
分页模式
GraphQL支持多种分页模式:
偏移分页:
type Query {
articles(limit: Int = 10, offset: Int = 0): ArticleList!
}
type ArticleList {
items: [Article!]!
total: Int!
hasMore: Boolean!
}
游标分页(Relay规范):
type Query {
articles(first: Int, after: String, last: Int, before: String): ArticleConnection!
}
type ArticleConnection {
edges: [ArticleEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ArticleEdge {
node: Article!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
游标分页查询示例:
query {
articles(first: 10, after: "YXJ0aWNsZToxMA==") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
内省(Introspection)
GraphQL支持内省查询,客户端可以查询Schema的完整结构。
查询类型信息
query {
__schema {
types {
name
kind
description
}
}
}
查询特定类型
query {
__type(name: "User") {
name
kind
fields {
name
type {
name
kind
}
args {
name
type {
name
}
}
}
}
}
内省的用途
- 自动生成API文档
- 生成TypeScript类型定义
- 支持IDE自动补全
- 动态构建查询
GraphQL vs REST
| 特性 | REST | GraphQL |
|---|---|---|
| 端点 | 多个端点 | 单一端点 |
| 数据获取 | 固定响应结构 | 客户端指定字段 |
| 请求次数 | 可能需要多次请求 | 一次请求获取所有数据 |
| 类型系统 | 无内置类型系统 | 强类型Schema |
| 缓存 | HTTP缓存机制完善 | 需要额外实现缓存 |
| 学习曲线 | 相对简单 | 需要学习查询语言 |
| 工具支持 | 广泛 | 丰富的生态系统 |
| 版本控制 | 通常通过URL版本控制 | 可以无版本演进 |
总结
GraphQL的核心概念:
- Schema:定义API的数据结构和操作
- 类型系统:强类型,支持标量、对象、枚举、接口、联合、输入类型
- 操作类型:Query(查询)、Mutation(变更)、Subscription(订阅)
- 内省:可以查询Schema的完整结构
GraphQL通过强类型系统和灵活的查询语言,解决了REST API的过度获取和获取不足问题,特别适合数据关系复杂、前端需求多样的场景。