
GraphQL 模式设计最佳实践
模式命名约定
# 类型:PascalCase
type User {
id: ID!
email: String!
createdAt: DateTime! # camelCase 字段
profilePhoto: URL # 描述性名称
}
# 输入类型:PascalCase + Input 后缀
input CreateUserInput {
email: String!
name: String!
role: UserRole = USER # 使用枚举并设置默认值
}
# 枚举:ALL_CAPS 值
enum UserRole {
ADMIN
USER
MODERATOR
}
# 变更:动词 + 名词
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
}
# 载荷类型:与领域类型分离
type CreateUserPayload {
user: User
errors: [UserError!]!
}
type UserError {
field: String
message: String!
code: UserErrorCode!
}

分页(基于游标)
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 Query {
users(
first: Int
after: String
last: Int
before: String
filter: UserFilter
orderBy: UserOrderBy
): UserConnection!
}
使用 DataLoader 解决 N+1 问题
import DataLoader from 'dataloader';
// 没有 DataLoader:N+1 查询
// 对于 100 篇文章,分别获取作者 100 次
// 使用 DataLoader:批处理 + 缓存
const userLoader = new DataLoader<string, User>(async (userIds) => {
const users = await db.query(
'SELECT * FROM users WHERE id = ANY($1)',
[userIds]
);
// 按 userIds 的顺序返回用户
const userMap = new Map(users.map(u => [u.id, u]));
return userIds.map(id => userMap.get(id) ?? new Error(`User ${id} not found`));
});
// 解析器
const resolvers = {
Post: {
author: (post: Post) => userLoader.load(post.authorId),
// 每个批次一次查询,而不是每篇文章一次
},
};
错误处理
// 一致的错误格式
const resolvers = {
Mutation: {
createUser: async (_: unknown, { input }: { input: CreateUserInput }) => {
try {
const validation = validateCreateUser(input);
if (!validation.ok) {
return {
user: null,
errors: validation.errors.map(e => ({
field: e.field,
message: e.message,
code: 'VALIDATION_ERROR',
})),
};
}
const user = await userService.create(input);
return { user, errors: [] };
} catch (err) {
if (err instanceof UniqueConstraintError) {
return {
user: null,
errors: [{ field: 'email', message: 'Email already in use', code: 'DUPLICATE_EMAIL' }],
};
}
throw err; // 让全局错误处理器处理意外错误
}
},
},
};
订阅
type Subscription {
orderUpdated(orderId: ID!): OrderUpdatedPayload!
newMessage(channelId: ID!): Message!
}
const resolvers = {
Subscription: {
orderUpdated: {
subscribe: (_, { orderId }) =>
pubsub.asyncIterator(`ORDER_UPDATED_${orderId}`),
resolve: (payload: OrderUpdatedPayload) => payload,
},
},
Mutation: {
updateOrder: async (_, { id, input }) => {
const order = await orderService.update(id, input);
await pubsub.publish(`ORDER_UPDATED_${id}`, { orderUpdated: order });
return { order, errors: [] };
},
},
};
模式演进
# 弃用旧字段,添加新字段
type User {
id: ID!
# 旧字段
fullName: String @deprecated(reason: "Use 'name' instead")
# 新字段
name: String!
}
良好的 GraphQL 模式设计使 API 自文档化且易于演进。