乐闻世界logo
搜索文章和话题

GraphQL Schema 设计有哪些最佳实践

2月21日 17:01

GraphQL Schema 设计最佳实践

GraphQL Schema 是 API 的核心,良好的 Schema 设计能够提高开发效率、减少维护成本,并提供更好的开发者体验。

1. Schema 基础结构

类型定义

graphql
type User { id: ID! name: String! email: String! age: Int posts: [Post!]! createdAt: DateTime! } type Post { id: ID! title: String! content: String! author: User! comments: [Comment!]! createdAt: DateTime! } type Comment { id: ID! text: String! author: User! post: Post! createdAt: DateTime! }

输入类型

graphql
input CreateUserInput { name: String! email: String! age: Int } input CreatePostInput { title: String! content: String! authorId: ID! }

枚举类型

graphql
enum PostStatus { DRAFT PUBLISHED ARCHIVED }

联合类型

graphql
union SearchResult = User | Post | Comment

接口类型

graphql
interface Node { id: ID! } type User implements Node { id: ID! name: String! email: String! } type Post implements Node { id: ID! title: String! content: String! }

2. 命名规范

类型命名

  • 使用 PascalCase(首字母大写)
  • 使用描述性名称
  • 避免缩写
  • 使用单数形式

示例:

graphql
# 好的命名 type UserProfile { } type Article { } type ShoppingCart { } # 不好的命名 type user { } type Art { } type ShopCart { }

字段命名

  • 使用 camelCase(首字母小写)
  • 使用动词或名词
  • 避免使用保留字

示例:

graphql
# 好的命名 type User { firstName: String! lastName: String! fullName: String! isActive: Boolean! } # 不好的命名 type User { first_name: String! LastName: String! Full_Name: String! active: Boolean! }

参数命名

  • 使用 camelCase
  • 描述性名称
  • 包含单位或类型信息

示例:

graphql
# 好的命名 query GetUsers($limit: Int, $offset: Int) { } query GetPosts($after: String, $first: Int) { } # 不好的命名 query GetUsers($l: Int, $o: Int) { } query GetPosts($a: String, $f: Int) { }

3. 类型设计原则

避免过度嵌套

graphql
# 不好的设计 - 过度嵌套 type User { posts { comments { author { posts { comments { # 无限嵌套 } } } } } } # 好的设计 - 合理嵌套 type User { posts(limit: Int): [Post!]! } type Post { comments(limit: Int): [Comment!]! }

使用输入类型封装参数

graphql
# 不好的设计 - 参数过多 mutation CreateUser($name: String!, $email: String!, $age: Int, $address: String, $phone: String) { } # 好的设计 - 使用输入类型 input CreateUserInput { name: String! email: String! age: Int address: String phone: String } mutation CreateUser($input: CreateUserInput!) { }

实现分页

graphql
# 基于游标的分页(推荐) type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! totalCount: Int! } type PostEdge { node: Post! cursor: String! } type Query { posts(after: String, first: Int): PostConnection! } # 基于偏移的分页 type PostResult { posts: [Post!]! total: Int! page: Int! pageSize: Int! } type Query { posts(offset: Int, limit: Int): PostResult! }

4. 错误处理

自定义错误类型

graphql
type Error { code: String! message: String! field: String } type UserResult { user: User errors: [Error!]! } type Mutation { createUser(input: CreateUserInput!): UserResult! }

使用可空字段

graphql
type Mutation { # 返回可空类型表示可能失败 createUser(input: CreateUserInput!): User }

5. 版本控制与演进

废弃字段

graphql
type User { id: ID! name: String! # 废弃字段,提供替代方案 fullName: String @deprecated(reason: "Use 'name' instead") email: String! }

添加新字段

graphql
type User { id: ID! name: String! email: String! # 新增字段,不影响现有客户端 phoneNumber: String }

6. 性能优化

使用 DataLoader

javascript
const userLoader = new DataLoader(async (userIds) => { const users = await User.findAll({ where: { id: userIds } }); return userIds.map(id => users.find(user => user.id === id)); });

字段级权限控制

graphql
type User { id: ID! name: String! # 敏感字段需要权限 email: String! @auth(requires: ADMIN) salary: Float @auth(requires: ADMIN) }

7. 文档与描述

添加描述

graphql
""" 用户类型,包含用户的基本信息 """ type User { """ 用户唯一标识符 """ id: ID! """ 用户姓名,最大长度 100 字符 """ name: String! """ 用户邮箱地址,必须是有效的邮箱格式 """ email: String! }

8. 最佳实践总结

  1. 保持简洁: 避免过度设计,保持 Schema 简洁明了
  2. 一致性: 在整个项目中保持命名和结构的一致性
  3. 可扩展性: 设计时考虑未来的扩展需求
  4. 向后兼容: 添加新功能时保持向后兼容
  5. 文档化: 为所有类型、字段和参数添加描述
  6. 性能考虑: 避免过度嵌套,合理使用分页
  7. 安全性: 实现适当的权限控制和验证
  8. 测试: 为 Resolver 编写单元测试和集成测试
标签:GraphQL