Паттерны API Gateway: Backend for Frontend, компоновка, агрегация и многое другое
Паттерны API Gateway — это проверенные архитектурные решения, которые реализуют на уровне шлюза для абстрагирования сложности от клиентов, повышения производительности и оркестрирования микросервисов. Знание этих паттернов позволяет принимать обоснованные архитектурные решения.
Что такое паттерны API Gateway?
Паттерны API Gateway — это архитектурные решения, которые используют API Gateway как центральный узел для решения повторяющихся проблем архитектуры API. Они определяют, как шлюз трансформирует, агрегирует, маршрутизирует и защищает запросы, не требуя этого от клиентов или бэкэндов.
Основные паттерны:
- Backend for Frontend (BFF): отдельный шлюз для каждого типа клиента
- API Composition / Aggregation: несколько обращений к бэкэнду в одном запросе
- Protocol Translation: преобразование протоколов на уровне шлюза
- Circuit Breaker: защита от каскадных отказов
- Strangler Fig: постепенная миграция старых API
- API Versioning: управление версиями и параллельные версии API
- 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 на новую архитектуру. Назван так по названию растения-душителя, которое медленно оплетает дерево и его заменяет.
Процесс:
- Шлюз маршрутизирует все запросы к старому API (фасад).
- Новые эндпоинты реализуются в новой системе. Шлюз маршрутизирует их к новой системе.
- Постепенно мигрируются дополнительные эндпоинты.
- Когда все эндпоинты мигрированы, старый 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?
2. Что такое API Composition?
3. Что такое Protocol Translation на API Gateway?
4. Как работает Circuit Breaker?
5. Что такое Strangler Fig Pattern?
6. Сколько BFF должно быть?
7. Что происходит при Composition, если бэкенд упадет?
8. Должен ли Gateway содержать бизнес-логику?
9. В чем разница между синхронной и асинхронной Composition?
10. Как правильно конфигурировать Circuit Breaker?
11. Что такое Request/Response Transformation?
12. Что такое API Versioning на Gateway?
13. Что такое Graceful Degradation в контексте API Composition?
14. В чем разница между API Gateway и Service Mesh?
15. Какой самый важный Anti-Pattern на API Gateway?
Продолжение в курсе по API
Следующий материал посвящен документированию API с помощью Swagger и OpenAPI — как сделать описание своих API понятным, доступным для машинной обработки и соответствующим стандартам.
Источники и дополнительные материалы
- https://microservices.io/patterns/apigateway.html
- https://samnewman.io/patterns/
- https://docs.konghq.com/hub/
- https://learn.microsoft.com/en-us/azure/api-management/
- https://martinfowler.com/bliki/StranglerFigApplication.html
Книги по разработке API
Keine Bücher für Kategorie "api-development" gefunden.


