GraphQL Federation:构建统一的微服务API网关

深入讲解GraphQL Federation架构模式,详解Apollo Federation、Schema stitching的实现原理,提供微服务间Schema合并、跨服务查询解析、类型扩展等实战案例。

引言

在微服务架构中,每个服务都有自己的API和数据模型。GraphQL Federation允许我们将多个独立的GraphQL Schema组合成一个统一的API图(Supergraph),让客户端像访问单一服务一样查询所有微服务的数据。

Federation vs Schema Stitching

特性FederationSchema 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核心价值:

  1. 服务自治:每个团队独立管理自己的Schema
  2. 统一API:客户端看到单一图,无需关心后端拆分
  3. 类型安全:编译时检查跨服务查询
  4. 灵活演进:通过指令实现平滑的Schema变更

适用场景:

  • 多团队协作的大型微服务系统
  • 需要统一API网关的前端团队
  • 数据关系复杂的领域模型

不适用场景:

  • 简单CRUD应用
  • 团队规模小(<3个服务)
  • 对查询性能有极致要求(考虑REST+缓存)

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页