Skip to content
IRC-CodingIRC-Coding
API Gateway PatternsBackend for FrontendBFFAPI CompositionProtocol TranslationAggregationCircuit BreakerStrangler FigMicroservices

Паттерны API Gateway: BFF, Composition и Protocol Translation

Паттерны API Gateway: BFF, API Composition, Protocol Translation, Circuit Breaker, Strangler Fig с примерами кода.

S

schutzgeist

12 min read
Паттерны API Gateway: BFF, Composition и Protocol Translation

Паттерны API Gateway: Backend for Frontend, компоновка, агрегация и многое другое

Паттерны API Gateway — это проверенные архитектурные решения, которые реализуют на уровне шлюза для абстрагирования сложности от клиентов, повышения производительности и оркестрирования микросервисов. Знание этих паттернов позволяет принимать обоснованные архитектурные решения.

Что такое паттерны API Gateway?

Паттерны API Gateway — это архитектурные решения, которые используют API Gateway как центральный узел для решения повторяющихся проблем архитектуры API. Они определяют, как шлюз трансформирует, агрегирует, маршрутизирует и защищает запросы, не требуя этого от клиентов или бэкэндов.

Основные паттерны:

  1. Backend for Frontend (BFF): отдельный шлюз для каждого типа клиента
  2. API Composition / Aggregation: несколько обращений к бэкэнду в одном запросе
  3. Protocol Translation: преобразование протоколов на уровне шлюза
  4. Circuit Breaker: защита от каскадных отказов
  5. Strangler Fig: постепенная миграция старых API
  6. API Versioning: управление версиями и параллельные версии API
  7. Request/Response Transformation: адаптация полезной нагрузки на уровне шлюза

Кто использует паттерны API Gateway?

  • Архитекторы ПО выбирают паттерны на основе требований
  • Platform-команды реализуют паттерны через конфигурацию шлюза
  • Разработчики бэкэнда должны понимать, как шлюз трансформирует их эндпоинты
  • Разработчики фронтэнда выигрывают от агрегированных и адаптированных ответов

Почему это важно в информатике и на экзаменах?

Паттерны API Gateway — это фундаментальное знание для сертификаций в области архитектуры (AWS Solutions Architect, Azure Solutions Architect) и подробно рассматриваются в книгах по микросервисам (Sam Newman, Chris Richardson). На экзаменах IHK и в курсах информатики паттерны типа BFF и Circuit Breaker спрашивают как примеры архитектурных паттернов проектирования. Понимание этих паттернов необходимо для проектирования масштабируемых и поддерживаемых API-ландшафтов.

Основные концепции в деталях

1. Backend for Frontend (BFF)

Паттерн BFF говорит: каждый тип клиента получает собственный шлюз (или отдельную конфигурацию шлюза), настроенный именно под его потребности.

Проблема: веб-приложение, мобильное приложение и B2B-клиент имеют разные требования. Веб-приложение нужны полные данные для сложных интерфейсов. Мобильному нужны сокращённые данные для медленных сетей. B2B-клиенту нужны batch-эндпоинты и XML. Единственный шлюз не может оптимально удовлетворить все эти требования одновременно.

Решение: три BFF:

  • Web-BFF: полные ответы, сложная агрегация, CORS
  • Mobile-BFF: сокращённые поля, плоские структуры, кэширование, более низкие rate limits
  • B2B-BFF: batch-эндпоинты, трансформация XML, mTLS, более высокие квоты

Каждый BFF разрабатывается, развёртывается и масштабируется независимо.

2. API Composition / Aggregation

Паттерн компоновки объединяет несколько обращений к бэкэнду в один запрос клиента. Клиент отправляет запрос на шлюз, шлюз параллельно вызывает несколько бэкэндов и комбинирует результаты.

Пример: клиент вызывает /api/dashboard. Шлюз параллельно обращается к:

  • /users/42 (User Service)
  • /orders?userId=42 (Order Service)
  • /notifications?userId=42 (Notification Service)

Шлюз объединяет три ответа в один JSON-ответ. Клиент делает один roundtrip вместо трёх.

Преимущества: сокращённая latency (параллельные вызовы), снижение сложности на клиенте, централизованная обработка ошибок. Недостатки: шлюз становится сложнее, обработка ошибок при частичных отказах затруднена.

3. Protocol Translation

Шлюз преобразует входящие протоколы во внутренние:

  • REST в gRPC: внешние клиенты отправляют REST, шлюз преобразует в gRPC для внутренних микросервисов. gRPC эффективнее (Protobuf, HTTP/2), но менее доступен для внешних клиентов.
  • SOAP в REST: устаревшие SOAP-сервисы предоставляются как современные REST-API.
  • HTTP в WebSocket: шлюз удерживает WebSocket-соединения и общается внутри через HTTP.
  • GraphQL в REST: шлюз принимает GraphQL-запросы и маппирует их на вызовы REST-бэкэнда.

4. Circuit Breaker

Circuit Breaker защищает систему от каскадных отказов. Он имеет три состояния:

  • Closed: запросы маршрутизируются нормально. Ошибки подсчитываются.
  • Open: при превышении порога частоты ошибок (например, 50% за 10 секунд) все запросы немедленно отклоняются с ошибкой. Бэкэнд больше не нагружается.
  • Half-Open: после периода ожидания (например, 30 секунд) проводится пробный запрос. Если он успешен, выключатель переходит в Closed. Если он неудачен, остаётся в Open.

Паттерн предотвращает блокировку всей системы, когда медленный или отказавший сервис заставляет клиентов ждать timeout.

5. Strangler Fig

Паттерн Strangler Fig позволяет постепенно мигрировать старый API на новую архитектуру. Назван так по названию растения-душителя, которое медленно оплетает дерево и его заменяет.

Процесс:

  1. Шлюз маршрутизирует все запросы к старому API (фасад).
  2. Новые эндпоинты реализуются в новой системе. Шлюз маршрутизирует их к новой системе.
  3. Постепенно мигрируются дополнительные эндпоинты.
  4. Когда все эндпоинты мигрированы, старый API отключается.

Преимущество: миграция без big-bang-релиза, возможность откатить, низкий риск.

6. API Versioning на шлюзе

Шлюз управляет несколькими версиями API параллельно:

  • /v1/users -> старый User Service (Legacy)
  • /v2/users -> новый User Service (с расширенными полями)

Шлюз может маппировать версии: запрос v1 трансформируется так, чтобы работать с бэкэндом v2 (Forward Compatibility). Или шлюз маршрутизирует v1 на старый и v2 на новый бэкэнд.

7. Request/Response Transformation

Шлюз трансформирует полезную нагрузку без изменения бэкэнда:

  • Сокращение полей: мобильный клиент получает 5 полей вместо 20
  • Переименование полей: first_name (снаружи) в firstName (внутри)
  • Конвертация формата: XML в JSON, snake_case в camelCase
  • Обогащение заголовков: security-заголовки, Correlation-ID, Tenant-ID
  • Фильтрация ответа: удаление sensitive-полей для отдельных клиентов

Почему паттерны API Gateway важны на практике?

Сценарий 1: производительность мобильного приложения

Мобильному приложению нужны 3 API-вызова для экрана дашборда. Каждый вызов имеет 200ms latency. Итого: 600ms плюс рендеринг. С API Composition: 1 вызов, параллельные обращения к бэкэнду, итого: 250ms. Приложение ощущается значительно быстрее.

Сценарий 2: миграция наследия

Монолит возрастом 10 лет нужно мигрировать на микросервисы. Big-bang-релиз слишком рискован. С паттерном Strangler Fig эндпоинты мигрируют поочередно, шлюз автоматически маршрутизирует на нужную систему. Старые клиенты ничего не замечают о миграции.

Сценарий 3: Предотвращение каскадного отказа

Сервис A вызывает сервис B, сервис B медленный. Сервис A ждёт timeout, занимает потоки. Несколько запросов накапливаются, сервис A тоже становится медленным. Circuit Breaker на шлюзе обнаруживает ошибки сервиса B, размыкает цепь, и сервис A получает ошибку немедленно вместо ожидания. Система остаётся стабильной.

Практический пример: Kong API Gateway с Composition и Circuit Breaker

Этот пример демонстрирует два паттерна в действии: API Composition (один endpointагрегирует несколько вызовов backend) и Circuit Breaker (защита от отказов backend). Они выбраны потому, что это самые частые паттерны в production и решают главные архитектурные проблемы: сложность на клиенте и устойчивость к сбоям.

# kong-patterns.yml
# API Gateway Patterns: Composition + Circuit Breaker + BFF

services:
  # --- Backend Services ---
  - name: user-service
    url: http://user-service.internal:3000

  - name: order-service
    url: http://order-service.internal:3001

  - name: notification-service
    url: http://notification-service.internal:3002

  # --- BFF: Mobile (урезанные данные) ---
  - name: mobile-bff
    url: http://bff-mobile.internal:4000
    routes:
      - name: mobile-dashboard
        paths:
          - /mobile/dashboard
        strip_path: false

  # --- BFF: Web (полные данные) ---
  - name: web-bff
    url: http://bff-web.internal:4001
    routes:
      - name: web-dashboard
        paths:
          - /web/dashboard
        strip_path: false

routes:
  # --- Composition Route: /dashboard агрегирует 3 сервиса ---
  - name: dashboard-composite
    paths:
      - /api/dashboard
    strip_path: false
    service: user-service  # Fallback, переопределяется плагином

plugins:
  # --- Circuit Breaker для всех сервисов ---
  - name: request-termination
    service: order-service
    config:
      status_code: 503
      message: "Order Service temporarily unavailable"

  # --- Rate Limiting по BFF ---
  - name: rate-limiting
    route: mobile-dashboard
    config:
      minute: 60
      limit_by: ip

  - name: rate-limiting
    route: web-dashboard
    config:
      minute: 200
      limit_by: consumer
// bff-composition.js
// Backend for Frontend: Dashboard Composition с Circuit Breaker
// Node.js/Express — агрегирует User, Orders и Notifications

const express = require('express');
const axios = require('axios');
const CircuitBreaker = require('opossum');

const app = express();

// Circuit Breaker для каждого сервиса
const userBreaker = new CircuitBreaker(async (userId) => {
  const res = await axios.get(`http://user-service.internal:3000/users/${userId}`, {
    timeout: 2000
  });
  return res.data;
}, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
});

const orderBreaker = new CircuitBreaker(async (userId) => {
  const res = await axios.get(`http://order-service.internal:3001/orders?userId=${userId}`, {
    timeout: 2000
  });
  return res.data;
}, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
});

const notificationBreaker = new CircuitBreaker(async (userId) => {
  const res = await axios.get(`http://notification-service.internal:3002/notifications?userId=${userId}`, {
    timeout: 2000
  });
  return res.data;
}, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
});

// Fallback-функции при размыкании цепи
userBreaker.fallback(() => ({ id: null, name: 'Неизвестен', error: 'User Service unavailable' }));
orderBreaker.fallback(() => ({ orders: [], error: 'Order Service unavailable' }));
notificationBreaker.fallback(() => ({ notifications: [], error: 'Notification Service unavailable' }));

// Composition endpoint: /api/dashboard?userId=42
app.get('/api/dashboard', async (req, res) => {
  const userId = req.query.userId;
  if (!userId) {
    return res.status(400).json({
      error: 'MISSING_PARAMETER',
      message: 'userId ist erforderlich'
    });
  }

  // Параллельные вызовы с Circuit Breaker
  const [user, orders, notifications] = await Promise.all([
    userBreaker.fire(userId),
    orderBreaker.fire(userId),
    notificationBreaker.fire(userId)
  ]);

  // Агрегированный ответ
  res.json({
    user: user,
    orders: orders.orders || [],
    orderCount: orders.orders ? orders.orders.length : 0,
    notifications: notifications.notifications || [],
    unreadNotifications: notifications.notifications
      ? notifications.notifications.filter(n => !n.read).length
      : 0,
    // Маркировка частичных отказов
    _meta: {
      userAvailable: !user.error,
      ordersAvailable: !orders.error,
      notificationsAvailable: !notifications.error,
      timestamp: new Date().toISOString()
    }
  });
});

// Mobile BFF: Урезанные поля
app.get('/mobile/dashboard', async (req, res) => {
  const userId = req.query.userId;
  const [user, orders, notifications] = await Promise.all([
    userBreaker.fire(userId),
    orderBreaker.fire(userId),
    notificationBreaker.fire(userId)
  ]);

  // Mobile: только существенные поля, плоская структура
  res.json({
    userName: user.name || 'Неизвестен',
    orderCount: orders.orders ? orders.orders.length : 0,
    unreadCount: notifications.notifications
      ? notifications.notifications.filter(n => !n.read).length
      : 0
  });
});

// Web BFF: Полные данные
app.get('/web/dashboard', async (req, res) => {
  const userId = req.query.userId;
  const [user, orders, notifications] = await Promise.all([
    userBreaker.fire(userId),
    orderBreaker.fire(userId),
    notificationBreaker.fire(userId)
  ]);

  // Web: все поля, вложенная структура
  res.json({
    profile: user,
    recentOrders: orders.orders || [],
    allNotifications: notifications.notifications || [],
    _meta: {
      userAvailable: !user.error,
      ordersAvailable: !orders.error,
      notificationsAvailable: !notifications.error
    }
  });
});

app.listen(4000, () => {
  console.log('BFF listening on port 4000');
});

Детальная информация

BFF: Когда и сколько?

Эмпирическое правило: один BFF на тип клиента, не на конкретное устройство. Типичные BFF:

  • Web-BFF: браузерные приложения (SPA, SSR)
  • Mobile-BFF: iOS/Android приложения
  • B2B-BFF: интеграции с партнёрами

Не рекомендуется: один BFF на экран или на фичу. Это приводит к взрыву количества BFF и проблемам с поддержкой.

Composition: Синхронная или асинхронная

Показанная composition синхронная (клиент ждёт ответ). Для асинхронной composition есть два варианта:

  • Request/Reply с WebSocket: клиент подписывается на шлюз, шлюз вызывает backends и пушит результаты по мере доступности.
  • Event-Driven: клиент отправляет запрос, шлюз запускает асинхронную обработку и возвращает ID корреляции. Клиент полит или подписывается на результат.

Конфигурация Circuit Breaker

Важные параметры:

  • errorThresholdPercentage: при каком проценте ошибок открывается breaker? (типично: 50%)
  • resetTimeout: как долго breaker остаётся открытым? (типично: 30с)
  • timeout: когда вызов считается失败? (типично: 2-5с)
  • volumeThreshold: минимальное количество вызовов перед подсчётом процента ошибок (типично: 5)

Strangler Fig: риски и защита

  • Дублирование логики: во время миграции логика существует в обеих системах одновременно. Решение: общие библиотеки для бизнес-логики.
  • Ошибки маршрутизации: шлюз направляет запрос в неправильную систему. Решение: feature-флаги и canary-релизы.
  • Консистентность данных: старая и новая системы используют одну БД или разные. Решение: четкое разделение данных или синхронизация read-моделей.

Трансформация: JSONPath и Templates

Современные API Gateway поддерживают трансформацию через JSONPath или templates:

  • JSONPath: $.user.first_name извлекает значение поля
  • JQ: сложные преобразования с синтаксисом jq
  • Liquid Templates: template-движок для трансформации ответов (Azure API Management)
  • Lua: Kong/OpenResty позволяют писать Lua-скрипты для сложных преобразований

Anti-Patterns

  • Gateway как слой бизнес-логики: шлюз не должен содержать бизнес-логику. Трансформация и агрегация допустимы, но не расчеты или валидация по правилам бизнеса.
  • Слишком много BFF: каждая команда хочет свой BFF. Это приводит к дублированию и увеличению затрат на поддержку.
  • Composition без timeouts: если бэкенд медленный, клиент ждет бесконечно. Каждый composition-запрос должен иметь timeout.
  • Circuit Breaker без fallback: без fallback клиент получает непонятное сообщение об ошибке. Определите осмысленные fallback-ответы.

FAQ: API Gateway Patterns

1. Что такое Backend for Frontend (BFF) Pattern?

BFF означает, что каждый тип клиента (Web, Mobile, B2B) получает собственный API Gateway или собственную конфигурацию шлюза, точно подогнанную под его потребности. Web-BFF возвращает полные данные, Mobile-BFF сокращает поля, B2B-BFF предоставляет batch-эндпоинты. Каждый BFF можно разрабатывать и развертывать независимо.

2. Что такое API Composition?

API Composition (или агрегация) означает, что шлюз разбивает запрос клиента на несколько запросов к бэкенду, собирает результаты и возвращает одну объединенную ответ. Клиент делает один roundtrip вместо нескольких. Это снижает latency благодаря параллельным вызовам и упрощает логику на стороне клиента.

3. Что такое Protocol Translation на API Gateway?

Protocol Translation означает, что шлюз преобразует входящий протокол в другой. Например, REST-запросы от внешних клиентов внутри преобразуются в gRPC, или SOAP legacy-сервисы предоставляются как REST снаружи. Это дает внешним клиентам простые протоколы, а внутри используются эффективные.

4. Как работает Circuit Breaker?

Circuit Breaker имеет три состояния: Closed (запросы пропускаются, ошибки считаются), Open (при превышении порога ошибок все запросы сразу отклоняются, бэкенд защищен) и Half-Open (после timeout проверяется один запрос). Это предотвращает каскадные сбои при медленных или упавших бэкендах.

5. Что такое Strangler Fig Pattern?

Strangler Fig Pattern позволяет постепенно мигрировать старое API. Изначально шлюз направляет все запросы в старую систему. Новые эндпоинты реализуют в новой системе и шлюз перенаправляет их туда. Постепенно все эндпоинты переходят. Когда миграция завершена, старая система отключается.

6. Сколько BFF должно быть?

Правило: один BFF на тип клиента, не на устройство или функцию. Типично три BFF: Web (браузер-приложения), Mobile (iOS/Android) и B2B (интеграции партнеров). Больше BFF ведет к дублированию и затратам на поддержку. Меньше BFF вынуждает разные клиенты в один формат.

7. Что происходит при Composition, если бэкенд упадет?

При сбое бэкенда шлюз должен предоставить fallback-значения и пометить ответ метаданными (например, _meta.ordersAvailable: false). Клиент получает частичный ответ вместо ошибки. Circuit Breaker предотвращает ожидание на timeouts. Частичный ответ позволяет graceful degradation на стороне клиента.

8. Должен ли Gateway содержать бизнес-логику?

Нет, шлюз не должен содержать бизнес-логику. Он может отвечать за трансформацию, агрегацию, аутентификацию и маршрутизацию, но не за расчеты и валидацию по правилам бизнеса. Бизнес-логика в шлюзе сложно тестируется, создает зависимости между командами и ухудшает архитектуру.

9. В чем разница между синхронной и асинхронной Composition?

Синхронная Composition: клиент ждет агрегированного ответа. Асинхронная Composition: клиент отправляет запрос, получает ID корреляции и позже опрашивает или подписывается на результат. Асинхронная лучше для долгоживущих агрегаций, синхронная для быстрых параллельных вызовов.

10. Как правильно конфигурировать Circuit Breaker?

Важные параметры: errorThresholdPercentage (обычно 50%, когда открыть breaker), resetTimeout (обычно 30s, как долго он остается открытым), timeout (обычно 2-5s, когда вызов считается ошибкой) и volumeThreshold (обычно 5, минимум вызовов перед расчетом процента ошибок). Значения зависят от сервиса — критичные сервисы требуют более быстрого breaker.

11. Что такое Request/Response Transformation?

Трансформация означает, что шлюз адаптирует payload без изменения бэкенда. Примеры: сокращение полей для мобильных клиентов, переименование полей (snake_case в camelCase), конвертация XML в JSON, добавление заголовков безопасности или удаление конфиденциальных полей для определенных клиентов. Современные шлюзы используют JSONPath, jq или Liquid Templates.

12. Что такое API Versioning на Gateway?

API Versioning на Gateway означает, что шлюз управляет несколькими версиями API параллельно. /v1/users направляется к старому бэкенду, /v2/users к новому. Шлюз может трансформировать версии, так чтобы v1-запрос работал и на v2-бэкенде. Это позволяет мигрировать без breaking changes для существующих клиентов.

13. Что такое Graceful Degradation в контексте API Composition?

Graceful Degradation означает, что при частичном сбое бэкенда API продолжает работать с сниженной функциональностью. Шлюз предоставляет fallback-значения для упавших сервисов и отмечает их в ответе. Клиент может показать отсутствующие данные (например, “Заказы временно недоступны”) вместо вывода ошибки на всю страницу.

14. В чем разница между API Gateway и Service Mesh?

API Gateway управляет внешним трафиком с паттернами вроде BFF, Composition и Auth. Service Mesh (Istio, Linkerd) управляет внутренней коммуникацией между микросервисами с mTLS, retry и Circuit Breaking. В современных архитектурах оба существуют: API Gateway снаружи для клиентов, Service Mesh внутри для service-to-service.

15. Какой самый важный Anti-Pattern на API Gateway?

Самый важный anti-pattern это использование Gateway как слоя бизнес-логики. Когда расчеты, валидация и workflow-логика попадают в шлюз, он становится сложно тестируемым, команды зависят друг от друга и шлюз превращается в bottleneck. Gateway должен содержать инфраструктурную и интеграционную логику, но не бизнес-логику.

Продолжение в курсе по API

Следующий материал посвящен документированию API с помощью Swagger и OpenAPI — как сделать описание своих API понятным, доступным для машинной обработки и соответствующим стандартам.

Источники и дополнительные материалы

  1. https://microservices.io/patterns/apigateway.html
  2. https://samnewman.io/patterns/
  3. https://docs.konghq.com/hub/
  4. https://learn.microsoft.com/en-us/azure/api-management/
  5. https://martinfowler.com/bliki/StranglerFigApplication.html

Книги по разработке API

Keine Bücher für Kategorie "api-development" gefunden.

Назад к блогу
Share:

Похожие статьи