Реализация GraphQL Federation для объединения нескольких сервисов

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация GraphQL Federation для объединения нескольких сервисов
Сложная
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

GraphQL Federation: объединение микросервисов в единый граф

GraphQL Federation (Apollo Federation 2) позволяет разным командам владеть разными частями GraphQL-схемы. Каждый сервис публикует свой subgraph, Router объединяет их в единый federated graph. Клиент видит одну точку входа, один язык запросов — не знает о внутренней микросервисной структуре.

Архитектура

Клиент → Router (Apollo Router) → Users Subgraph
                                → Products Subgraph
                                → Orders Subgraph
                                → Reviews Subgraph

Router принимает GraphQL-запрос, строит план выполнения (query plan) и параллельно или последовательно обращается к нужным subgraph'ам, объединяя результаты.

Subgraph: Users Service

# users-service/schema.graphql
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.3",
        import: ["@key", "@shareable"])

type Query {
  me: User
  user(id: ID!): User
}

type User @key(fields: "id") {
  id: ID!
  name: String!
  email: String!
  createdAt: String!
}
// users-service/server.js
import { ApolloServer } from '@apollo/server'
import { buildSubgraphSchema } from '@apollo/subgraph'
import { gql } from 'graphql-tag'

const typeDefs = gql`...` // schema выше

const resolvers = {
  Query: {
    me: (parent, args, context) => context.db.users.findById(context.userId),
    user: (parent, { id }, context) => context.db.users.findById(id)
  },

  User: {
    // Reference resolver: Router запрашивает User по id из другого сервиса
    __resolveReference: async ({ id }, context) => {
      return context.db.users.findById(id)
    }
  }
}

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers })
})

Subgraph: Products Service

# products-service/schema.graphql
extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.3",
        import: ["@key", "@external", "@requires", "@provides"])

type Query {
  products(categoryId: ID, limit: Int): [Product!]!
  product(id: ID!): Product
}

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
  stock: Int!
  categoryId: ID!
}

# Расширение типа User из users-service
type User @key(fields: "id") {
  id: ID!
  # Добавляем поле wishlist к типу User, которым владеет users-service
  wishlist: [Product!]!
}
const resolvers = {
  Query: {
    products: (parent, { categoryId, limit = 20 }, ctx) =>
      ctx.db.products.find({ categoryId, limit }),
    product: (parent, { id }, ctx) => ctx.db.products.findById(id)
  },

  Product: {
    __resolveReference: ({ id }, ctx) => ctx.db.products.findById(id)
  },

  User: {
    __resolveReference: ({ id }) => ({ id }),  // stub — данные User берёт users-service
    wishlist: ({ id }, args, ctx) => ctx.db.wishlists.getByUserId(id)
  }
}

Subgraph: Orders Service

# orders-service/schema.graphql
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!
  createdAt: String!

  # @external — поле принадлежит users-service
  user: User! @external
  userId: ID! @external

  items: [OrderItem!]!
}

type OrderItem {
  product: Product!  # ссылка на тип из products-service
  quantity: Int!
  price: Float!
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
  CANCELLED
}

type Query {
  order(id: ID!): Order
  myOrders: [Order!]!
}

# Расширяем User: добавляем orders к пользователю
type User @key(fields: "id") {
  id: ID! @external
  orders: [Order!]!
}

# Расширяем Product: добавляем orderCount
type Product @key(fields: "id") {
  id: ID! @external
  orderCount: Int!
}

Apollo Router: конфигурация

# router.yaml
supergraph:
  listen: 0.0.0.0:4000

# Subgraph endpoints
subgraphs:
  users:
    routing_url: http://users-service:4001/graphql
  products:
    routing_url: http://products-service:4002/graphql
  orders:
    routing_url: http://orders-service:4003/graphql
  reviews:
    routing_url: http://reviews-service:4004/graphql

# Заголовки: прокинуть Authorization во все subgraph'ы
headers:
  all:
    request:
      - propagate:
          matching: ^Authorization$

# Корс
cors:
  origins:
    - https://app.example.com
  allow_headers:
    - Content-Type
    - Authorization

# Трассировка
telemetry:
  tracing:
    propagation:
      request:
        header_name: traceparent
    exporters:
      jaeger:
        endpoint: http://jaeger:14268/api/traces

# Limits
limits:
  max_depth: 15
  max_aliases: 30
  max_root_fields: 20

Supergraph composition (CI/CD)

# Установить Rover CLI
curl -sSL https://rover.apollo.dev/nix/latest | sh

# Публикация subgraph в Apollo Studio (или локально)
rover subgraph publish my-graph@prod \
  --name users \
  --schema ./users-service/schema.graphql \
  --routing-url https://users.internal/graphql

# Локальная composición для тестов
rover supergraph compose --config supergraph.yaml > supergraph.graphql
# supergraph.yaml (для локальной разработки)
federation_version: =2.3.5

subgraphs:
  users:
    routing_url: http://localhost:4001/graphql
    schema:
      file: ./users-service/schema.graphql
  products:
    routing_url: http://localhost:4002/graphql
    schema:
      file: ./products-service/schema.graphql

Пример federated запроса

# Один запрос охватывает данные из 3 сервисов
query OrderDetail($orderId: ID!) {
  order(id: $orderId) {
    id
    status
    total
    user {           # → users-service
      name
      email
    }
    items {
      quantity
      price
      product {      # → products-service
        name
        stock
      }
    }
  }
}

Router строит query plan:

  1. Получить order из orders-service → получить userId и productId[]
  2. Параллельно: fetch user из users-service и product[] из products-service
  3. Merge и вернуть клиенту

Entity batching

Router автоматически батчирует запросы к entities через _entities query:

# Router отправляет это в products-service
query {
  _entities(representations: [
    { __typename: "Product", id: "1" },
    { __typename: "Product", id: "2" },
    { __typename: "Product", id: "3" }
  ]) {
    ... on Product { name stock }
  }
}

Поэтому __resolveReference должен поддерживать DataLoader для батчинга:

import DataLoader from 'dataloader'

User: {
  __resolveReference: async ({ id }, context) => {
    // DataLoader батчирует все __resolveReference вызовы одного запроса
    return context.loaders.userById.load(id)
  }
}

Срок выполнения

Настройка Apollo Federation 2 с Router, composition pipeline и 3–5 subgraph'ами — 5–8 рабочих дней.