名称: graphql-schema-design 用户可调用: false 描述: 用于设计GraphQL模式,包括类型系统、SDL模式、字段设计、分页策略、指令和版本控制策略,以创建可维护和可扩展的API。 允许工具: []
GraphQL 模式设计
应用GraphQL模式设计原则来创建结构良好、可维护和可扩展的GraphQL API。本技能涵盖类型系统、模式定义语言(SDL)、字段设计模式、分页策略、指令和模式演变技术。
核心类型系统
对象类型
对象类型是GraphQL模式的基本构建块。每个对象类型表示可以从服务中获取的一种对象及其字段。
type User {
id: ID!
username: String!
email: String!
createdAt: DateTime!
posts: [Post!]!
profile: Profile
}
type Post {
id: ID!
title: String!
content: String!
author: User!
publishedAt: DateTime
tags: [String!]!
}
接口类型
接口定义抽象类型,多个对象类型可以实现。当多个类型共享公共字段时使用接口。
interface Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
}
interface Timestamped {
createdAt: DateTime!
updatedAt: DateTime!
}
type Article implements Node & Timestamped {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
title: String!
content: String!
author: User!
}
type Comment implements Node & Timestamped {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
text: String!
author: User!
post: Post!
}
联合类型
联合类型表示可以是几种对象类型之一的值。当字段可以返回没有共享字段的不同类型时使用联合。
union SearchResult = Article | User | Tag | Comment
type Query {
search(query: String!): [SearchResult!]!
}
# 查询示例
query {
search(query: "graphql") {
__typename
... on Article {
title
content
}
... on User {
username
email
}
... on Tag {
name
count
}
}
}
枚举类型
枚举定义字段的特定允许值集。用于具有固定选项的字段以确保类型安全。
enum UserRole {
ADMIN
MODERATOR
USER
GUEST
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
DELETED
}
enum SortOrder {
ASC
DESC
}
type User {
id: ID!
role: UserRole!
status: AccountStatus!
}
enum AccountStatus {
ACTIVE
SUSPENDED
DEACTIVATED
}
输入类型
输入类型用于查询和变更中的复杂参数。允许您将结构化数据作为单个参数传递。
input CreateUserInput {
username: String!
email: String!
password: String!
profile: UserProfileInput
}
input UserProfileInput {
firstName: String
lastName: String
bio: String
avatarUrl: String
}
input UpdatePostInput {
title: String
content: String
status: PostStatus
tags: [String!]
}
input PostFilterInput {
status: PostStatus
authorId: ID
tags: [String!]
createdAfter: DateTime
createdBefore: DateTime
}
type Mutation {
createUser(input: CreateUserInput!): User!
updatePost(id: ID!, input: UpdatePostInput!): Post!
}
type Query {
posts(filter: PostFilterInput, limit: Int): [Post!]!
}
自定义标量
自定义标量使用领域特定类型扩展内置标量类型(String、Int、Float、Boolean、ID)。
scalar DateTime
scalar EmailAddress
scalar URL
scalar JSON
scalar UUID
scalar PositiveInt
scalar Currency
type User {
id: UUID!
email: EmailAddress!
website: URL
createdAt: DateTime!
metadata: JSON
age: PositiveInt
}
type Product {
id: ID!
price: Currency!
images: [URL!]!
}
指令
指令提供修改执行行为或向模式添加元数据的方式。
# 内置指令
type Query {
# 如果条件为真则跳过字段
user(id: ID!): User @skip(if: $skipUser)
# 仅在条件为真时包含字段
posts: [Post!]! @include(if: $includePosts)
}
type Post {
id: ID!
title: String!
# 标记字段为已弃用,带迁移提示
oldTitle: String @deprecated(reason: "使用 'title' 代替")
}
# 自定义指令
directive @auth(requires: UserRole!) on FIELD_DEFINITION
directive @rateLimit(max: Int!, window: Int!) on FIELD_DEFINITION
directive @cacheControl(
maxAge: Int!
scope: CacheScope = PUBLIC
) on FIELD_DEFINITION | OBJECT
enum CacheScope {
PUBLIC
PRIVATE
}
type Query {
me: User @auth(requires: USER)
adminPanel: AdminData @auth(requires: ADMIN)
publicPosts: [Post!]!
@cacheControl(maxAge: 300)
@rateLimit(max: 100, window: 60)
}
可空与非可空设计
在模式设计中仔细考虑可空性。非可空字段提供更强保证但降低灵活性。
type User {
# 必需字段 - 永远不会为空
id: ID!
username: String!
email: String!
# 可选字段 - 可能为空
bio: String
website: URL
# 非可空列表带可空项
# 列表本身永远不会为空,但项可以
favoriteColors: [String]!
# 可空列表带非可空项
# 列表可以为空,但如果存在,项不会为空
phoneNumbers: [String!]
# 非可空列表带非可空项
# 列表和项都不会为空
roles: [UserRole!]!
# 可选关系
profile: Profile
# 必需关系
account: Account!
}
分页模式
基于偏移的分页
使用限制和偏移的简单分页。易于实现,但在大偏移时有性能问题。
type Query {
posts(limit: Int = 10, offset: Int = 0): PostsResult!
}
type PostsResult {
posts: [Post!]!
total: Int!
hasMore: Boolean!
}
# 查询示例
query {
posts(limit: 20, offset: 40) {
posts {
id
title
}
total
hasMore
}
}
基于游标的分页(连接)
对大数据集更高效,支持双向分页。基于Relay连接规范。
type Query {
posts(
first: Int
after: String
last: Int
before: String
): PostConnection!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# 查询示例
query {
posts(first: 10, after: "Y3Vyc29yOjEw") {
edges {
cursor
node {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
变更设计模式
输入对象模式
对变更使用输入对象,以便于演变和参数更好组织。
type Mutation {
createPost(input: CreatePostInput!): CreatePostPayload!
updatePost(input: UpdatePostInput!): UpdatePostPayload!
deletePost(input: DeletePostInput!): DeletePostPayload!
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
tags: [String!]
publishedAt: DateTime
}
type CreatePostPayload {
post: Post
errors: [UserError!]
success: Boolean!
}
type UserError {
message: String!
field: String
code: String!
}
input UpdatePostInput {
id: ID!
title: String
content: String
status: PostStatus
}
type UpdatePostPayload {
post: Post
errors: [UserError!]
success: Boolean!
}
模式中的错误处理
设计模式以支持字段级和变更级错误处理。
type Mutation {
# 选项1:联合返回类型
login(email: String!, password: String!): LoginResult!
}
union LoginResult = LoginSuccess | LoginError
type LoginSuccess {
user: User!
token: String!
expiresAt: DateTime!
}
type LoginError {
message: String!
code: LoginErrorCode!
}
enum LoginErrorCode {
INVALID_CREDENTIALS
ACCOUNT_LOCKED
EMAIL_NOT_VERIFIED
}
# 选项2:带错误数组的有效负载
type Mutation {
updateUser(input: UpdateUserInput!): UpdateUserPayload!
}
type UpdateUserPayload {
user: User
errors: [UserError!]
success: Boolean!
}
模式拼接和联合基础
模式联合
通过跨服务定义实体和扩展类型来设计联合模式。
# 用户服务
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
}
# 帖子服务
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
author: User!
}
# 评论服务
extend type Post @key(fields: "id") {
id: ID! @external
reviews: [Review!]!
}
type Review {
id: ID!
rating: Int!
comment: String
post: Post!
}
版本控制策略
字段弃用
标记字段为已弃用,同时保持向后兼容性。
type User {
id: ID!
name: String! @deprecated(
reason: "使用 'firstName' 和 'lastName' 代替"
)
firstName: String!
lastName: String!
email: String! @deprecated(
reason: "从ContactInfo中使用 'primaryEmail'"
)
contactInfo: ContactInfo!
}
type ContactInfo {
primaryEmail: String!
secondaryEmails: [String!]!
}
加法变更
添加新字段和类型而不破坏现有查询。
# 版本1
type Post {
id: ID!
title: String!
content: String!
}
# 版本2 - 加法变更
type Post {
id: ID!
title: String!
content: String!
# 新添加的字段
summary: String
readingTime: Int
tags: [Tag!]!
}
# 新添加的类型
type Tag {
id: ID!
name: String!
color: String
}
最佳实践
- 使用有意义名称:为类型、字段和参数选择清晰、描述性名称,反映其目的和领域。
- 设计可空字段:默认使字段可空,除非能保证值始终存在。
- 优先输入对象:对变更中的复杂参数使用输入类型,以便于演变。
- 实现分页:始终对可能无限增长的列表字段使用基于游标或基于偏移的分页模式。
- 对固定集使用枚举:为具有有限可能值的字段定义枚举以确保类型安全。
- 记录模式:使用GraphQL描述语法为类型、字段和参数添加描述。
- 通过弃用版本控制:使用@deprecated指令而非删除字段以保持向后兼容性。
- 仔细设计变更:返回包含结果和潜在错误的有效负载类型。
- 保持模式扁平:避免可能导致复杂查询和N+1问题的深层嵌套类型。
- 明智使用接口:为跨多个类型的共享字段定义接口以启用多态查询。
常见陷阱
- 模式设计中的过度获取:创建返回整个对象的字段,而仅需要特定数据。
- 不足获取:未提供足够相关数据,迫使客户端进行多次请求。
- 循环依赖:创建类型间循环引用而无仔细解析器设计。
- 破坏性变更:未经过弃用期就删除或重命名字段,破坏现有客户端。
- 无分页:返回无界列表,可能导致数据增长时的性能问题。
- 不一致命名:跨类型对类似字段使用不同约定。
- 过度使用非可空:使太多字段非可空,降低模式灵活性和韧性。
- 缺失错误处理:未在变更有效负载中设计适当错误处理。
- 忽略N+1问题:创建固有导致N+1查询问题的模式设计。
- 差输入验证:未定义输入类型上的约束,导致运行时验证问题。
何时使用此技能
使用GraphQL模式设计技能当:
- 从头设计新GraphQL API
- 重构现有GraphQL模式以获得更好结构
- 向现有模式添加新功能
- 从REST迁移到GraphQL
- 跨微服务实现模式联合
- 为性能和可维护性优化模式
- 为团队建立模式设计标准
- 审查和改进模式设计模式
- 为长期API演变和版本控制设计
- 创建可重用模式模式和约定
资源
- GraphQL规范 - 官方GraphQL规范
- GraphQL最佳实践 - 官方最佳实践指南
- Apollo模式设计指南 - 模式设计模式
- Relay连接规范 - 基于游标的分页标准
- GraphQL模式设计书 - 深入模式设计资源