从REST迁移到GraphQL不是"大爆炸"式的切换——在一个已有几十个REST端点的系统中一次性切换到GraphQL,风险太高。正确的做法是渐进式迁移:先引入GraphQL作为一层BFF(Backend for Frontend),逐步替代REST端点,最终REST可以保留、废弃或完全迁移。
在现有REST API前面加一层GraphQL服务,作为前端的新入口:
// GraphQL BFF服务 const typeDefs = `#graphql type User { id: ID! name: String! email: String! avatarUrl: String posts(limit: Int = 10): [Post!]! } type Post { id: ID! title: String! content: String! author: User! likesCount: Int! } type Query { user(id: ID!): User post(id: ID!): Post feed(limit: Int = 20): [Post!]! } `; const resolvers = { Query: { user: async (_, { id }, context) => { // 调用已有的REST API const userData = await context.rest.get(`/api/users/${id}`); return { ...userData, avatarUrl: userData.avatar_url, // 字段名转换 }; }, feed: async (_, { limit }) => { const posts = await context.rest.get('/api/feed', { params: { limit } }); return posts; } }, User: { posts: async (parent, { limit }, context) => { return context.rest.get(`/api/users/${parent.id}/posts`, { params: { limit } }); } }, Post: { author: async (parent, context) => { return context.rest.get(`/api/users/${parent.author_id}`); }, likesCount: (parent) => parent.likes_count, } }; const server = new ApolloServer({ typeDefs, resolvers, context: () => ({ rest: new RESTDataSource({ baseURL: 'https://api.example.com' }), }), });
这种方式的优势:
REST和GraphQL长期共存,各取所长:
// 同一个Express/Koa应用中同时支持REST和GraphQL app.use('/api/rest/*', restRouter); // 旧API app.use('/graphql', graphqlMiddleware); // 新API app.use('/api/v2/*', restV2Router); // 新REST端点(内部微服务)
这种模式下:
如果决定逐步把后端也迁移到GraphQL,可以按数据域逐步替换:
每个阶段的节奏取决于团队资源和业务压力。一个有50个REST端点的系统,通常需要6-12个月完成完整迁移。
过度设计Schema:迁移初期不要试图在Schema中映射所有REST端点的功能。先覆盖80%的核心查询场景,剩余的逐步补充。
忽略性能差异:GraphQL的一个请求可能调用多个REST API。如果原来的REST端点是有缓存和优化的,GraphQL BFF层的调用模式可能不同,导致性能退化。
缓存策略缺失:REST有HTTP缓存,GraphQL没有。迁移后需要建立GraphQL自己的缓存体系(客户端缓存 + 服务端缓存 + DataLoader)。
团队培训不足:GraphQL的学习曲线比REST陡峭。迁移前至少给前端和后端团队各安排一次培训,否则效率会先降后升。
监控空白期:迁移过程中,部分流量走REST、部分走GraphQL。监控需要同时覆盖两条路径,避免盲区。
如何判断迁移是否成功?几个可量化的指标:
不是所有 REST 端点都值得迁。选第一批端点的标准是:高频、多端复用、需要聚合。典型候选是用户详情、首页信息流、订单详情——它们被 Web 和移动端同时调用,而且每个页面都要拼多个 REST 调用。反之,低频的内部管理端点、文件流端点、第三方回调端点应该留在 REST,它们的迁移收益低、风险高。
一个务实的排序:先迁"读多写少"的查询场景,把 BFF 层跑稳,再逐步迁移写操作(Mutation)。写操作涉及事务和幂等,迁移时最容易出问题,放在后面用更充分的测试覆盖。
任何迁移都要留退路。推荐的三级回滚机制:
// 灰度开关示意:按用户 id 决定走哪条路径 function isGraphQLUser(userId: string): boolean { const hash = hashCode(userId) % 100; return hash < getConfig().graphqlPercent; // 从 5 逐步调到 100 }
迁移完成不代表结束,还要回答几个问题:GraphQL 的 P95 延迟是否达到预期?前端是否真的减少了请求数?缓存体系是否建立(客户端规范化缓存、DataLoader 批处理、服务端查询级缓存)?团队是否掌握了新的调试方式?如果这些答案不理想,往往不是"GraphQL 不行",而是迁移时把 REST 的坏习惯带了过来——比如在解析器里逐条调用 REST 端点、没有批处理、字段级缓存缺失。对照第三章和第四章的实践重新检查,通常能找到症结。
