Реалізація 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 }), // заглушка для полів @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 та збагачення даних.
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: {
// Тільки власник або адміністратор бачить деталі замовлення
__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 додаткові тижні







