引言
在微服务架构中,每个服务都有自己的API和数据模型。GraphQL Federation允许我们将多个独立的GraphQL Schema组合成一个统一的API图(Supergraph),让客户端像访问单一服务一样查询所有微服务的数据。
Federation vs Schema Stitching
| 特性 | Federation | Schema Stitching |
|---|---|---|
| 所有者 | Apollo(标准化) | 社区(多种实现) |
| Schema控制 | 各服务自治 | 集中式合并 |
| 扩展性 | 高 | 中 |
| 类型扩展 | ✅ 支持 | 需手动配置 |
| 工具链 | Apollo Studio | 自定义 |
Apollo Federation架构
┌─────────────────────────────────────────────────────┐
│ Gateway (Router) │
│ 统一的GraphQL入口点 │
└──────────────┬──────────────────────────────────────┘
│
┌──────────┼──────────┬──────────────┐
↓ ↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ User │ │ Order │ │Product │ │Review │
│Service │ │Service │ │Service │ │Service │
└────────┘ └────────┘ └────────┘ └────────┘
子图(Subgraph)定义
用户服务
# users-service/schema.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int = 10, offset: Int = 0): [User!]!
}
// users-service/index.js
const { ApolloServer } = require('@apollo/server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const { gql } = require('graphql-tag');
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
}
`;
const resolvers = {
User: {
// Federation解析器:通过ID获取完整User
__resolveReference: async (reference) => {
return await userService.getUserById(reference.id);
},
},
Query: {
user: async (_, { id }) => {
return await userService.getUserById(id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
订单服务(扩展User类型)
# orders-service/schema.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@requires"])
# 扩展User类型(定义在其他服务)
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
totalSpent: Float!
}
type Order @key(fields: "id") {
id: ID!
user: User!
items: [OrderItem!]!
totalAmount: Float!
status: OrderStatus!
createdAt: DateTime!
}
type OrderItem {
product: Product!
quantity: Int!
unitPrice: Float!
}
enum OrderStatus {
PENDING
PAID
SHIPPED
DELIVERED
CANCELLED
}
type Query {
order(id: ID!): Order
ordersByUser(userId: ID!): [Order!]!
}
// orders-service/resolvers.js
const resolvers = {
Order: {
__resolveReference: async (reference) => {
return await orderService.getOrderById(reference.id);
},
user: (order) => {
// 返回User的引用(仅包含key字段)
return { __typename: 'User', id: order.userId };
},
items: async (order) => {
return await orderService.getOrderItems(order.id);
},
},
User: {
// 扩展User的orders字段
orders: async (user) => {
return await orderService.getOrdersByUserId(user.id);
},
totalSpent: async (user) => {
return await orderService.getTotalSpentByUserId(user.id);
},
},
};
产品服务
# products-service/schema.graphql
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
description: String
category: Category!
reviews: [Review!]!
averageRating: Float
}
type Category @key(fields: "id") {
id: ID!
name: String!
products: [Product!]!
}
extend type OrderItem @key(fields: "id") {
id: ID! @external
product: Product!
}
type Query {
product(id: ID!): Product
products(categoryId: ID, limit: Int = 20): [Product!]!
}
Gateway配置
Apollo Router
# router.yaml
supergraph:
listen: 0.0.0.0:4000
introspection: true
subgraphs:
users:
routing_url: http://users-service:4001/graphql
schema:
subgraph_url: http://users-service:4001/graphql
orders:
routing_url: http://orders-service:4002/graphql
schema:
subgraph_url: http://orders-service:4002/graphql
products:
routing_url: http://products-service:4003/graphql
schema:
subgraph_url: http://products-service:4003/graphql
reviews:
routing_url: http://reviews-service:4004/graphql
schema:
subgraph_url: http://reviews-service:4004/graphql
telemetry:
metrics:
prometheus:
enabled: true
listen: 0.0.0.0:9090
# 启动Apollo Router
router --supergraph supergraph.graphql --config router.yaml
# 或使用Rover CLI生成supergraph
rover supergraph compose --config supergraph.yaml > supergraph.graphql
跨服务查询
查询示例
# 客户端查询(Gateway自动拆解分发)
query GetUserWithOrders {
user(id: "123") {
id
username
email
orders {
id
totalAmount
status
items {
product {
name
price
reviews {
rating
comment
}
}
quantity
}
}
totalSpent
}
}
查询执行计划
Query Plan:
Sequence {
Fetch(service: "users") {
user(id: "123") {
id username email
}
}
Parallel {
Flatten(path: "user") {
Fetch(service: "orders") {
... on User { id }
orders { id totalAmount status }
totalSpent
}
}
Flatten(path: "user.orders.@.items") {
Fetch(service: "products") {
... on OrderItem { id }
product { name price }
}
}
Flatten(path: "user.orders.@.items.@.product") {
Fetch(service: "reviews") {
... on Product { id }
reviews { rating comment }
}
}
}
}
类型扩展指令
# @key: 定义实体的唯一标识
type User @key(fields: "id") { ... }
# 复合key
type ProductVariant @key(fields: "productId size") { ... }
# 多个key(支持多种查找方式)
type User @key(fields: "id") @key(fields: "email") { ... }
# @external: 标记外部定义的字段
extend type User @key(fields: "id") {
id: ID! @external
email: String! @external # 从其他服务获取
}
# @requires: 声明依赖的外部字段
extend type Product @key(fields: "id") {
id: ID! @external
weight: Float @external
shippingCost: Float @requires(fields: "weight")
}
# @provides: 声明本服务能提供的额外字段
extend type Order @key(fields: "id") {
id: ID!
product: Product! @provides(fields: "name price")
}
性能优化
查询缓存
// Gateway层查询缓存
const { InMemoryLRUCache } = require('@apollo/utils.keyvaluecache');
const cache = new InMemoryLRUCache({
maxSize: 100 * 1024 * 1024, // 100MB
});
// 基于查询签名缓存响应
async function cachedQuery(query, variables) {
const signature = hashQuery(query, variables);
const cached = await cache.get(signature);
if (cached) {
return JSON.parse(cached);
}
const result = await executeQuery(query, variables);
// 根据TTL策略缓存
await cache.set(signature, JSON.stringify(result), {
ttl: result.cacheControl.maxAge || 60,
});
return result;
}
数据加载器(DataLoader)
// 子图服务中使用DataLoader避免N+1查询
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (userIds) => {
const users = await userService.getUsersByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
const resolvers = {
Order: {
user: (order) => userLoader.load(order.userId),
},
};
Schema版本管理与演进
# 使用@deprecated指令平滑演进
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
# 新字段
displayName: String!
# 旧字段标记为废弃
fullName: String @deprecated(reason: "Use displayName instead")
}
# 使用@inaccessible隐藏内部字段
type Product @key(fields: "id") {
id: ID!
name: String!
internalCode: String @inaccessible
}
总结
GraphQL Federation核心价值:
- 服务自治:每个团队独立管理自己的Schema
- 统一API:客户端看到单一图,无需关心后端拆分
- 类型安全:编译时检查跨服务查询
- 灵活演进:通过指令实现平滑的Schema变更
适用场景:
- 多团队协作的大型微服务系统
- 需要统一API网关的前端团队
- 数据关系复杂的领域模型
不适用场景:
- 简单CRUD应用
- 团队规模小(<3个服务)
- 对查询性能有极致要求(考虑REST+缓存)
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。