返回首页
⚙️ 后端 / 架构
GraphQL Federation 实战:从单体到分布式 GraphQL
GraphQL Federation 让多个服务的 GraphQL 合并成单一 API。本文演示从单体 GraphQL 迁移到 Federation 的完整流程。
GraphQL · Federation · 微服务 · API
📰
今日技术简讯
📰 技术简讯 · 2026-06-24
今日聚合 6 条热门技术内容。
🤖 AI / LLM
1. OpenAI 推出 Realtime Voice API
- 链接:https://platform.openai.com/docs/realtime-voice
- 来源:OpenAI
- 摘要:实时语音对话 API v2,延迟 < 250ms,支持多语种切换。
⚙️ 后端 / 架构
2. GraphQL Federation 实战
- 链接:https://www.apollographql.com/blog/federation-2026
- 来源:Apollo
- 摘要:GraphQL Federation 2.5 实战指南,subgraph 拆分策略。
3. Apache Kafka 4.0 GA
- 链接:https://kafka.apache.org/blog/4-0-ga
- 来源:Apache Kafka
- 摘要:Kafka 4.0 正式 GA,KRaft 默认启用,移除 ZK 依赖。
🎨 前端 / Web
4. SvelteKit 2.5 发布
- 链接:https://kit.svelte.dev/blog/sveltekit-2-5
- 来源:Svelte
- 摘要:SvelteKit 2.5 优化 RSC 支持,与 Next.js App Router 体验对齐。
🚀 独立开发 / OPC
5. Lemon Squeezy 推出 Tax Automation
- 链接:https://www.lemonsqueezy.com/tax
- 来源:Lemon Squeezy
- 摘要:自动计算全球销售税,Merchant of Record 模式更完善。
6. Cal.com 推出 White Label
- 链接:https://cal.com/white-label
- 来源:Cal.com
- 摘要:自托管 + 品牌定制,企业用户专属。
数据来源:HN / Reddit / 各厂博客 采集时间:2026-06-24 09:00 (UTC+8)
📝
今日深度文
GraphQL Federation 实战:从单体到分布式 GraphQL
一句话结论:GraphQL Federation 是"GraphQL 版的微服务"。它解决了"前端复杂查询"与"后端独立演进"的矛盾。
背景
GraphQL Federation 是 Apollo 提出的"分布式 GraphQL"标准。它解决的问题:
单团队 GraphQL:
- 一个团队维护 schema
- 后端紧耦合
- 难以拆分
GraphQL Federation:
- 多个团队各自的 subgraph
- Gateway 合并 schema
- 各团队独立演进
核心概念
Subgraph(子图)
每个子服务定义自己的 GraphQL schema:
# users-service/schema.graphql
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
}
Supergraph(超图)
Gateway 把所有子图合并:
# 自动生成的 supergraph
type User @key(fields: "id") {
id: ID!
name: String! # 来自 users-service
email: String! # 来自 users-service
posts: [Post!]! # 来自 posts-service
}
Entity(实体)
跨服务的对象(User / Product 等)通过 entity 关联:
# posts-service/schema.graphql
type Post @key(fields: "id") {
id: ID!
title: String!
author: User! # 跨服务引用 User
}
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
实战:搭建 Federation 系统
架构
┌─────────────────┐
│ Client (App) │
└────────┬────────┘
│ GraphQL query
↓
┌─────────────────┐
│ Router │ ← Apollo Router / Gateway
│ (Supergraph) │
└────────┬────────┘
│ 分发
┌─────┼─────┬─────────┐
↓ ↓ ↓ ↓
users posts products reviews
Step 1:Users Subgraph
// users-service/src/server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
const typeDefs = `#graphql
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/key/v0.2",
import: ["@key", "@requires", "@external"])
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
`;
const resolvers = {
Query: {
user: (_, { id }) => db.users.findUnique({ where: { id } }),
users: () => db.users.findMany(),
},
User: {
__resolveReference: (ref) =>
db.users.findUnique({ where: { id: ref.id } }),
},
};
const schema = buildSubgraphSchema({ typeDefs, resolvers });
const server = new ApolloServer({ schema });
const { url } = await startStandaloneServer(server, {
listen: { port: 4001 },
});
Step 2:Posts Subgraph
// posts-service/src/server.ts
const typeDefs = `#graphql
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
authorId: ID!
}
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Query {
post(id: ID!): Post
postsByAuthor(authorId: ID!): [Post!]!
}
`;
const resolvers = {
Query: {
post: (_, { id }) => db.posts.findUnique({ where: { id } }),
postsByAuthor: (_, { authorId }) =>
db.posts.findMany({ where: { authorId } }),
},
User: {
posts: (user) =>
db.posts.findMany({ where: { authorId: user.id } }),
},
Post: {
__resolveReference: (ref) =>
db.posts.findUnique({ where: { id: ref.id } }),
},
};
const schema = buildSubgraphSchema({ typeDefs, resolvers });
// 端口 4002
Step 3:Products Subgraph
// products-service/src/server.ts
const typeDefs = `#graphql
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
}
extend type User @key(fields: "id") {
id: ID! @external
favoriteProducts: [Product!]!
}
type Query {
product(id: ID!): Product
}
`;
// 端口 4003
Step 4:Router / Gateway
# router-config.yaml
supergraph:
introspection: true
federation_version: =2.5.0
routers:
- port: 4000
# 用 Apollo Router
./router --config router-config.yaml --supergraph supergraph.graphql
或者用 GraphOS Studio 部署 supergraph。
Step 5:客户端查询
# 客户端发起一个查询,自动跨服务合并
query GetUserWithPostsAndProducts {
user(id: "123") {
name
email
posts {
title
content
}
favoriteProducts {
name
price
}
}
}
Router 自动:
- 调用 users-service 获取用户基本信息
- 调用 posts-service 获取 posts
- 调用 products-service 获取 favoriteProducts
- 合并成一个响应
高级特性
@requires(跨服务字段验证)
type Post @key(fields: "id") {
id: ID!
title: String!
author: User!
isPublic: Boolean! # 只有 author 标记为 public 时才返回
}
extend type User @key(fields: "id") {
id: ID! @external
isVerified: Boolean! @external
}
extend type Post @key(fields: "id") {
id: ID! @external
isPublic: Boolean! @requires(fields: "author { isVerified }")
}
@provides(减少跨服务调用)
extend type User @key(fields: "id") {
id: ID! @external
email: String! @external
posts: [Post!]! @provides(fields: "title")
}
性能优化
1. 数据加载器
import DataLoader from 'dataloader';
// 批量加载,避免 N+1
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids as string[] } },
});
return ids.map(id => users.find(u => u.id === id));
});
2. 持久化查询
// 客户端使用预编译的查询 ID
const QUERY_GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
posts { title }
}
}
`;
// 通过 Apollo Studio 配置允许的查询
// 防止恶意查询
3. 缓存
// Gateway 级别缓存
const { ApolloServer } = require('@apollo/server');
const responseCachePlugin = require('@apollo/server-plugin-response-cache').default;
const server = new ApolloServer({
schema,
plugins: [
responseCachePlugin({
sessionId: async ({ request }) => request.headers.get('user-id'),
maxAge: 300, // 5 分钟
}),
],
});
5 个常见坑
坑 1:Entity 字段不一致
// ❌ User.id 在不同 subgraph 是 string vs int
// Type mismatch 错误
// ✅ 统一约定(全部 string)
坑 2:循环依赖
// ❌ User 需要 Post,Post 需要 User.author
// → 死循环
// ✅ 拆解依赖
// User 只负责用户信息
// Post.author 是单独查询
坑 3:N+1 问题
// ❌ 1000 个 Post,每个都单独查 author
const Post = {
author: async (post) => db.users.findUnique({ where: { id: post.authorId } }),
};
// ✅ 用 DataLoader 批处理
const userLoader = new DataLoader(...);
const Post = {
author: async (post) => userLoader.load(post.authorId),
};
坑 4:版本管理
// 不同 subgraph 独立部署
// 但 schema 必须保持兼容
// ✅ 用 Federation SDL 描述
// ✅ CI 检查 schema 兼容性
坑 5:监控困难
分布式 GraphQL 调试比单体复杂:
- 一个查询跨多个服务
- 需要 tracing
✅ 用 Apollo Studio / OpenTelemetry
何时用 Federation
✅ 适合
- 团队 > 10 人,多团队协作
- 不同团队维护不同业务模块
- 已有 GraphQL 经验
❌ 不适合
- 小项目(单体 GraphQL 足够)
- 团队不熟悉 GraphQL
- 强一致性要求(分布式 GraphQL 弱一致性)
我的看法
GraphQL Federation 不是"GraphQL 替代品",而是"GraphQL 演进":
- 小项目:单体 GraphQL(一个团队)
- 中项目:模块化 GraphQL(一个仓库,多模块)
- 大项目:Federation(多团队)
不要为了"分布式"硬上 Federation:
- 团队 < 5 人,单体足够
- 没有跨团队 schema 合并需求,先单团队 GraphQL
- 迁移时机:团队拆分成多个独立小组
未来值得关注:
- Federation 3.0:更简化的 subgraph 协议
- Cosmo Router:开源 Federation Router
- GraphQL Fusion:新规范,可能统一 Federation 生态
参考
本文示例基于 Apollo Federation 2.5 + GraphQL 16.x,2026 年 6 月最新版本。
📚 同主题文章
⚙️后端 / 架构·
从单体到微服务:一家 5 人小团队的架构演进实录
我们 5 人团队 2 年内从 PHP 单体迁移到 Go 微服务的过程,包括踩过的 7 个坑和最终的技术栈选型。
微服务架构演进数据库
⚙️后端 / 架构·
gRPC vs REST 2026 对比:何时该用哪个?
gRPC 性能远超 REST,但 REST 仍是主流。本文用真实数据对比,给出 2026 年的选型指南。
gRPCRESTAPI
⚙️后端 / 架构·
微服务 vs 单体:2026 年该怎么选?
Martin Fowler 2026 年新文章认为"模块化单体"是新趋势。本文用真实数据对比两种架构的优劣,给出 2026 年的选型指南。
微服务单体架构