Реализация GraphQL Federation для объединения микросервисов
GraphQL Federation позволяет построить единый граф данных из нескольких независимых GraphQL-сервисов. Клиент делает один запрос к Federation Gateway, который собирает данные из разных subgraph-сервисов и возвращает единый ответ. Каждая команда владеет своим subgraph и деплоит его независимо.
Архитектура Federation
Клиент (браузер/мобильное)
│
▼
Federation Gateway (Apollo Router / Apollo Gateway)
│
┌────┴────┬──────────┬──────────┐
▼ ▼ ▼ ▼
User Order Product Review
Subgraph Subgraph Subgraph Subgraph
(Node.js) (Go) (Python) (Node.js)
│ │ │
Postgres Postgres MongoDB
Subgraph: User Service
// user-service/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
createdAt: DateTime!
}
type Query {
me: User
user(id: ID!): User
}
`;
const resolvers = {
User: {
// Reference resolver — позволяет другим сервисам расширять User
__resolveReference: async ({ id }) => {
return userRepository.findById(id);
}
},
Query: {
me: (_, __, { userId }) => userRepository.findById(userId),
user: (_, { id }) => userRepository.findById(id)
}
};
export const schema = buildSubgraphSchema({ typeDefs, resolvers });
Subgraph: Order Service — расширяет тип User
// order-service/schema.ts
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3",
import: ["@key", "@external", "@requires"])
type Order @key(fields: "id") {
id: ID!
status: OrderStatus!
total: Float!
items: [OrderItem!]!
customer: User! # ссылка на тип из User subgraph
createdAt: DateTime!
}
# Расширяем тип User из другого subgraph
type User @key(fields: "id") {
id: ID! @external # поле принадлежит User subgraph
orders(limit: Int = 10): [Order!]!
orderStats: OrderStats!
}
type OrderStats {
totalOrders: Int!
totalSpent: Float!
lastOrderAt: DateTime
}
enum OrderStatus { PENDING PAID SHIPPED DELIVERED CANCELLED }
type Query {
order(id: ID!): Order
orders(customerId: ID, status: OrderStatus): [Order!]!
}
`;
const resolvers = {
User: {
__resolveReference: async ({ id }) => ({ id }), // stub, поля @external
orders: async ({ id }, { limit }) =>
orderRepository.findByCustomerId(id, limit),
orderStats: async ({ id }) =>
orderRepository.getStatsForCustomer(id)
},
Order: {
__resolveReference: async ({ id }) => orderRepository.findById(id),
customer: ({ customerId }) => ({ __typename: 'User', id: customerId })
}
};
Apollo Router (Federation Gateway)
# router.yaml
federation_version: 2.3
supergraph:
listen: 0.0.0.0:4000
subgraphs:
users:
routing_url: http://user-service:4001/graphql
orders:
routing_url: http://order-service:4002/graphql
products:
routing_url: http://product-service:4003/graphql
cors:
origins:
- https://app.example.com
headers:
all:
request:
- propagate:
named: Authorization
- propagate:
named: X-Correlation-Id
# Запуск через Docker
docker run -p 4000:4000 \
-v $(pwd)/router.yaml:/dist/config/router.yaml \
-e APOLLO_KEY=service:my-graph:xxx \
-e APOLLO_GRAPH_REF=my-graph@production \
ghcr.io/apollographql/router:latest
Клиентский запрос через Federation
Клиент пишет запрос как будто это единый граф:
query GetUserDashboard($userId: ID!) {
user(id: $userId) {
name
email
orders(limit: 5) { # данные из Order subgraph
id
status
total
items {
product { # данные из Product subgraph
name
imageUrl
}
quantity
}
}
orderStats {
totalOrders
totalSpent
}
}
}
Router автоматически строит план выполнения: запросить user из User subgraph, затем параллельно запросить orders из Order subgraph и enrichment данных.
Managed Federation (Apollo Studio)
При Managed Federation схемы subgraph публикуются в Apollo Studio Registry:
# В CI/CD пайплайне
rover subgraph publish my-graph@production \
--schema ./schema.graphql \
--name orders \
--routing-url http://order-service:4002/graphql
Router загружает актуальную supergraph-схему автоматически при изменении любого subgraph. Публикация с проверкой совместимости — rover subgraph check.
Авторизация на уровне subgraph
const resolvers = {
Order: {
// Только владелец или admin видит детали заказа
__resolveReference: async ({ id }, { user }) => {
const order = await orderRepository.findById(id);
if (!order) return null;
if (order.customerId !== user.id && !user.roles.includes('admin')) {
throw new ForbiddenError('Access denied');
}
return order;
}
}
};
Сроки реализации
- 2–3 subgraph с базовой Federation — 2–3 недели
- Apollo Router + Managed Federation + CI-проверки совместимости — ещё 1 неделя
- Сложные @requires, @provides, nested resolvers — 1–2 дополнительные недели







