GraphQL vs REST:选哪个?

REST 用了五年,GraphQL 用了两年。对比一下。

REST 的优缺点

优点

  1. 简单直观
  2. HTTP 语义明确
  3. 缓存机制成熟
  4. 生态完善

缺点

  1. 过度获取/获取不足
  2. 接口数量多
  3. 版本管理麻烦

GraphQL 的优缺点

优点

  1. 按需获取
  2. 单一入口
  3. 类型系统
  4. 文档自动生成

缺点

  1. 学习曲线陡
  2. 缓存复杂
  3. 安全性需要注意

对比

维度RESTGraphQL
学习成本
灵活性一般
缓存简单复杂
调试简单需要工具
文档需要额外维护自动生成
错误处理HTTP 状态码自定义

场景对比

REST 更适合

场景原因
简单 CRUD没必要用 GraphQL
公共 APIREST 更通用
需要强缓存HTTP 缓存机制成熟
小团队不需要额外学习成本

GraphQL 更适合

场景原因
复杂数据关系一次查询获取关联数据
多端适配不同端需求不同
频繁迭代Schema 即文档
聚合多个数据源BFF 层

GraphQL 实战

Schema 定义

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users: [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(name: String!, email: String!): User!
  createPost(title: String!, content: String!, authorId: ID!): Post!
}

Resolver 实现

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      return dataSources.userAPI.getUser(id);
    },
    users: async (_, __, { dataSources }) => {
      return dataSources.userAPI.getUsers();
    },
  },
  User: {
    posts: async (user, _, { dataSources }) => {
      return dataSources.postAPI.getPostsByUserId(user.id);
    },
  },
};

客户端查询

query GetUserWithPosts($id: ID!) {
  user(id: $id) {
    name
    email
    posts {
      title
    }
  }
}

一次请求获取用户和文章,不需要多次请求。

GraphQL 查询界面

性能考虑

N+1 问题

query {
  users {
    name
    posts {
      title
    }
  }
}

如果每个用户的 posts 都单独查询,性能很差。

解决:DataLoader 批量加载

const DataLoader = require('dataloader');

const postLoader = new DataLoader(async (userIds) => {
  const posts = await getPostsByUserIds(userIds);
  return userIds.map((id) => posts.filter((p) => p.authorId === id));
});

// 在 Resolver 中使用
User: {
  posts: async (user, _, { postLoader }) => {
    return postLoader.load(user.id);
  },
},

查询复杂度

恶意用户可能发起深度嵌套查询:

query {
  users {
    posts {
      author {
        posts {
          author {
            # 无限嵌套...
          }
        }
      }
    }
  }
}

解决:限制查询深度和复杂度

import { createComplexityLimitRule } from 'graphql-validation-complexity';

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

const validationRules = [
  createComplexityLimitRule(1000, {
    onCost: (cost) => console.log('query cost:', cost),
  }),
];

缓存策略

REST 缓存

GET /api/users/1
Cache-Control: max-age=3600

浏览器和 CDN 自动缓存。

GraphQL 缓存

由于是 POST 请求,需要手动处理:

  1. 客户端缓存:Apollo Client / Relay
  2. 服务端缓存:响应缓存 + DataLoader
// Apollo Server 缓存
const server = new ApolloServer({
  typeDefs,
  resolvers,
  cache: new RedisCache({
    host: 'localhost',
  }),
});

迁移经验

从 REST 迁移到 GraphQL:

  1. 先用 GraphQL 包装 REST 接口
  2. 逐步将逻辑下沉到 Resolver
  3. 最后移除中间层
// GraphQL 包装 REST
const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const response = await fetch(`/api/users/${id}`);
      return response.json();
    },
  },
};

团队反馈

我们团队用 GraphQL 两年后做的调研:

反馈比例
很满意60%
一般30%
想换回 REST10%

满意的原因:灵活、类型安全。 不满的原因:调试困难、缓存复杂。

总结

没有绝对的好坏,看场景选择:

  • 简单 API、公共接口 → REST
  • 复杂数据、多端需求 → GraphQL

不要为了技术而技术。如果 REST 够用,没必要换 GraphQL。