正在加载,请稍候…

GraphQL 模式设计:最佳实践与真实模式

设计可扩展的健壮 GraphQL 模式。学习命名约定、分页模式、错误处理、使用 DataLoader 解决 N+1 问题以及模式演进策略。

GraphQL Schema Design: Best Practices and Real-World Patterns

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!
}

GraphQL Schema Design: Best Practices and Real-World Patterns illustration

分页(基于游标)

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!
}

GraphQL Schema Design: Best Practices and Real-World Patterns illustration

使用 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),
    // 每个批次一次查询,而不是每篇文章一次
  },
};

GraphQL Schema Design: Best Practices and Real-World Patterns illustration

错误处理

// 一致的错误格式
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 自文档化且易于演进。