Graph QL Schema
GraphQL-Schema
#resource / graphql
#type / concept
#status / growing
GraphQL-Schema
基础概念
什么是 GraphQL Schema
GraphQL Schema 是 GraphQL API 的核心,它定义了 API 的结构、可用的操作、数据类型以及各字段之间的关系。Schema 充当客户端和服务器之间的契约,明确规定了客户端可以请求哪些数据以及如何请求。
Schema 的作用
- 类型定义:定义数据的结构和关系
- API 契约:明确客户端可以执行的操作
- 文档化:自动生成 API 文档
- 验证:确保请求的合法性
- 工具支持:为开发工具提供类型信息
Schema 核心组件
- 类型系统(Type System)
- 根类型(Root Types)
- 字段解析器(Resolvers)
- 指令(Directives)
- 模式定义语言(Schema Definition Language, SDL)
使用指南
基本类型系统
标量类型(Scalar Types):
# 内置标量类型
scalar Int # 32位整数
scalar Float # 双精度浮点数
scalar String # UTF-8字符串
scalar Boolean # true/false
scalar ID # 唯一标识符
# 自定义标量类型
scalar DateTime
scalar Email
scalar URL
scalar JSON
对象类型(Object Types):
type User {
id: ID!
username: String!
email: Email!
profile: Profile
posts: [Post!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Profile {
id: ID!
firstName: String
lastName: String
bio: String
avatar: URL
user: User!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
tags: [Tag!]!
publishedAt: DateTime
isPublished: Boolean!
comments: [Comment!]!
}
枚举类型(Enum Types):
enum UserRole {
ADMIN
MODERATOR
USER
GUEST
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum SortOrder {
ASC
DESC
}
接口类型(Interface Types):
interface Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
}
interface Timestamped {
createdAt: DateTime!
updatedAt: DateTime!
}
type User implements Node & Timestamped {
id: ID!
username: String!
email: Email!
createdAt: DateTime!
updatedAt: DateTime!
}
type Post implements Node & Timestamped {
id: ID!
title: String!
content: String!
createdAt: DateTime!
updatedAt: DateTime!
}
联合类型(Union Types):
union SearchResult = User | Post | Comment
type Query {
search(query: String!): [SearchResult!]!
}
输入类型(Input Types)
input CreateUserInput {
username: String!
email: Email!
password: String!
profile: CreateProfileInput
}
input CreateProfileInput {
firstName: String
lastName: String
bio: String
}
input UpdateUserInput {
username: String
email: Email
profile: UpdateProfileInput
}
input UpdateProfileInput {
firstName: String
lastName: String
bio: String
avatar: String
}
input UserFilters {
role: UserRole
isActive: Boolean
createdAfter: DateTime
createdBefore: DateTime
}
input PaginationInput {
first: Int
after: String
last: Int
before: String
}
根类型定义
# 查询根类型
type Query {
# 单个资源查询
user(id: ID!): User
post(id: ID!): Post
# 列表查询
users(
filters: UserFilters
pagination: PaginationInput
sort: UserSortInput
): UserConnection!
posts(
filters: PostFilters
pagination: PaginationInput
): PostConnection!
# 搜索
search(query: String!, type: SearchType): [SearchResult!]!
# 当前用户
me: User
}
# 变更根类型
type Mutation {
# 用户操作
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
# 文章操作
createPost(input: CreatePostInput!): CreatePostPayload!
updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
deletePost(id: ID!): DeletePostPayload!
publishPost(id: ID!): PublishPostPayload!
# 认证操作
login(email: String!, password: String!): AuthPayload!
logout: LogoutPayload!
refreshToken(token: String!): AuthPayload!
}
# 订阅根类型
type Subscription {
# 用户相关订阅
userCreated: User!
userUpdated(id: ID!): User!
# 文章相关订阅
postPublished: Post!
postUpdated(authorId: ID!): Post!
# 评论相关订阅
commentAdded(postId: ID!): Comment!
}
连接和分页模式
# Relay-style连接
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# 简单分页
type UserList {
users: [User!]!
pagination: PaginationInfo!
}
type PaginationInfo {
page: Int!
limit: Int!
total: Int!
totalPages: Int!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
错误处理模式
# 统一的错误处理
interface MutationPayload {
success: Boolean!
message: String
errors: [UserError!]
}
type UserError {
field: String
message: String!
code: String
}
type CreateUserPayload implements MutationPayload {
success: Boolean!
message: String
errors: [UserError!]
user: User
}
type UpdateUserPayload implements MutationPayload {
success: Boolean!
message: String
errors: [UserError!]
user: User
}
# 结果联合类型
union CreateUserResult = CreateUserSuccess | ValidationError | DuplicateError
type CreateUserSuccess {
user: User!
}
type ValidationError {
field: String!
message: String!
}
type DuplicateError {
field: String!
value: String!
message: String!
}
指令(Directives)
# 内置指令
type User {
id: ID!
username: String!
email: String! @deprecated(reason: "Use profile.email instead")
secretField: String @skip(if: $skipSecret)
adminField: String @include(if: $isAdmin)
}
# 自定义指令
directive @auth(role: UserRole = USER) on FIELD_DEFINITION
directive @rate_limit(max: Int!, duration: Int!) on FIELD_DEFINITION
directive @cache(ttl: Int!) on FIELD_DEFINITION
type Query {
users: [User!]! @auth(role: ADMIN) @rate_limit(max: 100, duration: 60)
me: User @auth @cache(ttl: 300)
}
实战经验
Schema 设计最佳实践
1. 命名约定:
# 使用 PascalCase 命名类型
type UserProfile {
id: ID!
}
# 使用 camelCase 命名字段
type User {
firstName: String!
lastName: String!
createdAt: DateTime!
}
# 使用 SCREAMING_SNAKE_CASE 命名枚举值
enum UserStatus {
ACTIVE
INACTIVE
SUSPENDED
PENDING_VERIFICATION
}
2. 可空性设计:
type User {
# 必需字段使用 !
id: ID!
username: String!
email: String!
# 可选字段不使用 !
profile: Profile
lastLoginAt: DateTime
# 列表字段的可空性设计
posts: [Post!]! # 列表不为空,元素不为空
tags: [String!] # 列表可为空,元素不为空(如果列表存在)
metadata: [String] # 列表和元素都可为空
}
3. 字段设计原则:
# 避免过度嵌套
type User {
id: ID!
profile: Profile!
# 避免这样的深层嵌套
# profile: { personal: { address: { country: { code } } } }
}
# 提供合理的默认值
type Query {
users(
first: Int = 10
orderBy: UserOrderBy = CREATED_AT
direction: SortDirection = DESC
): [User!]!
}
# 使用描述性的字段名
type Post {
publishedAt: DateTime # 而不是 date
isPublished: Boolean # 而不是 status
viewCount: Int # 而不是 views
}
模块化 Schema 设计
1. 按领域分割:
# user.graphql
type User {
id: ID!
username: String!
email: String!
}
extend type Query {
user(id: ID!): User
users: [User!]!
}
extend type Mutation {
createUser(input: CreateUserInput!): User!
}
# post.graphql
type Post {
id: ID!
title: String!
author: User!
}
extend type Query {
post(id: ID!): Post
posts: [Post!]!
}
extend type Mutation {
createPost(input: CreatePostInput!): Post!
}
# 在用户类型上扩展文章字段
extend type User {
posts: [Post!]!
}
2. 使用 Schema 拼接:
// schema/index.js
import { makeExecutableSchema } from '@graphql-tools/schema';
import { mergeTypeDefs } from '@graphql-tools/merge';
import userTypeDefs from './user.graphql';
import postTypeDefs from './post.graphql';
import baseTypeDefs from './base.graphql';
const typeDefs = mergeTypeDefs([
baseTypeDefs,
userTypeDefs,
postTypeDefs
]);
export const schema = makeExecutableSchema({
typeDefs,
resolvers
});
性能优化 Schema 设计
1. N+1 查询问题解决:
type Post {
id: ID!
title: String!
author: User! # 可能导致 N+1 查询
comments: [Comment!]! # 可能导致 N+1 查询
}
# 解决方案:使用 DataLoader
type Query {
posts: [Post!]!
}
// 使用 DataLoader 解决 N+1 问题
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
const users = await User.findByIds(userIds);
return userIds.map(id => users.find(user => user.id === id));
});
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId)
}
};
2. 分页和限制:
type Query {
posts(
first: Int = 10
after: String
# 强制限制最大请求数量
): PostConnection! @constraint(maxFirst: 100)
}
directive @constraint(
maxFirst: Int
) on FIELD_DEFINITION
版本控制策略
1. 字段弃用:
type User {
id: ID!
username: String!
email: String! @deprecated(reason: "Use profile.email instead")
profile: Profile!
}
type Profile {
email: String!
# 新的邮箱字段位置
}
2. 渐进式迁移:
# 旧版本字段保持向后兼容
type User {
id: ID!
name: String! @deprecated(reason: "Use firstName and lastName instead")
firstName: String!
lastName: String!
}
安全考虑
1. 查询深度限制:
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(10)]
});
2. 查询复杂度分析:
import costAnalysis from 'graphql-cost-analysis';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
scalarCost: 1,
objectCost: 2,
listFactor: 10
})
]
});
3. 授权指令:
directive @auth(role: UserRole) on FIELD_DEFINITION
type Query {
adminUsers: [User!]! @auth(role: ADMIN)
me: User @auth
}
经验总结
优势
- 强类型系统:编译时类型检查,减少运行时错误
- 自文档化:Schema 即文档,自动生成 API 文档
- 灵活查询:客户端可按需请求数据
- 工具生态:丰富的开发工具支持
- 版本演进:通过字段弃用实现向后兼容
Schema 设计原则
- 以客户端需求为导向:设计 Schema 时考虑客户端的实际使用场景
- 保持简单性:避免过度复杂的嵌套和抽象
- 一致性:保持命名约定和设计模式的一致性
- 可扩展性:设计时考虑未来的扩展需求
- 性能考虑:避免可能导致性能问题的设计
常见问题与解决方案
-
过度获取数据
- 使用细粒度的字段设计
- 提供合理的默认查询
-
查询复杂度过高
- 实施查询复杂度分析
- 设置查询深度限制
-
N+1 查询问题
- 使用 DataLoader 模式
- 合理设计批量查询接口
-
Schema 演进困难
- 使用字段弃用而非删除
- 保持向后兼容性
适用场景
适合使用 GraphQL Schema:
- 复杂的数据关系
- 多样化的客户端需求
- 需要灵活查询的场景
- 快速迭代的产品
不适合使用 GraphQL:
- 简单的 CRUD 操作
- 文件上传下载
- 实时性要求极高的场景
- 缓存策略复杂的场景
信息参考
- GraphQL 官方文档
- GraphQL Schema 语言
- Apollo Server 文档
- GraphQL Tools
- GraphQL Best Practices
- Schema Design Guide
- GraphQL 规范
- Awesome GraphQL
- GraphQL Schema Design Patterns
- Production Ready GraphQL
Related notes
- 所属 MOC: 后端开发MOC