API Gateway для Swift-сервисов: строим мост между микросервисами
Представьте, что ваш backend — это огромный торговый центр. Внутри — десятки магазинов (микросервисов): авторизация, каталог товаров, корзина, платежи, уведомления. Клиенту (iOS-приложению) нужно зайти в каждый магазин отдельно — это хаос, долго и небезопасно. API Gateway — это главный вход, охрана и навигатор в одном флаконе. Он принимает запросы, проверяет пропуска, перенаправляет в нужный отдел и возвращает результат.
В мире Swift, где Vapor и Hummingbird набирают обороты, создание собственного API Gateway — не роскошь, а необходимость для масштабируемых проектов. Сегодня мы разберем, как написать легковесный gateway на Swift, который объединит ваши сервисы в единую экосистему.
Зачем нужен API Gateway в Swift-архитектуре?
Когда у вас 2-3 микросервиса, клиент может общаться с каждым напрямую. Но когда их 10+ (типичный сценарий для финтеха, e-commerce или SaaS), возникают проблемы:
- Раздутый клиентский код — каждый сервис имеет свой endpoint, формат ошибок, протокол аутентификации
- Уязвимости — каждый сервис вынужден сам заниматься CORS, rate limiting, проверкой JWT
- Сложность рефакторинга — переименовали сервис? Меняйте код на всех клиентах
- Лишний трафик — клиент делает 5 запросов вместо одного, чтобы собрать страницу
API Gateway решает эти задачи, становясь единой точкой входа. Он может агрегировать данные из нескольких микросервисов в один ответ, применять политики безопасности и логировать все запросы. Для Swift-разработчиков это ещё и возможность использовать строгую типизацию языка на уровне шлюза.
Выбор инструмента: Vapor + Hummingbird
Для создания API Gateway на Swift идеально подходит Vapor — самый зрелый веб-фреймворк экосистемы. Он даёт middleware, маршрутизацию, клиентские запросы (через Client) и поддержку async/await. Альтернатива — Hummingbird (лёгкий, быстрый, от Apple), но для примера возьмём Vapor как более документированный.
Наш gateway будет выполнять три функции:
- Проксирование запросов к нужному микросервису
- Аутентификация через JWT
- Агрегация данных из двух сервисов в один ответ
Шаг 1. Базовая структура проекта
Создаём новый проект Vapor:
vapor new APIGateway --template=api
cd APIGateway
Добавим в Package.swift зависимость для JWT:
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0")
И подключим в target:
.target(name: "App", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "JWT", package: "jwt")
])
Шаг 2. Middleware для аутентификации
Создадим middleware, который проверяет JWT-токен и передаёт информацию о пользователе дальше. Это классический паттерн для API Gateway на Swift:
import Vapor
import JWT
struct JWTAuthenticator: AsyncMiddleware {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
guard let token = request.headers.bearerAuthorization?.token else {
throw Abort(.unauthorized, reason: "Missing Bearer token")
}
// Проверяем подпись и срок действия
let payload = try await request.jwt.verify(token, as: UserPayload.self)
// Сохраняем пользователя в хранилище запроса
request.storage.set(UserPayloadKey.self, to: payload)
return try await next.respond(to: request)
}
}
struct UserPayload: JWTPayload {
var userID: UUID
var role: String
var exp: ExpirationClaim
func verify(using signer: JWTSigner) throws {
try exp.verifyNotExpired()
}
}
struct UserPayloadKey: StorageKey {
typealias Value = UserPayload
}
Обратите внимание: мы используем StorageKey — это Swift-способ передавать данные между middleware без глобальных переменных. Чисто, типобезопасно, по-свифтовому.
Шаг 3. Проксирование запросов к микросервисам
Сердце API Gateway — маршрутизация. Создадим контроллер, который перенаправляет запросы:
import Vapor
struct ProxyController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let protected = routes.grouped(JWTAuthenticator())
// Прокси к сервису пользователей
protected.group("api", "users") { group in
group.get(use: proxyToUserService)
group.post(use: proxyToUserService)
}
// Прокси к сервису заказов
protected.group("api", "orders") { group in
group.get(use: proxyToOrderService)
}
}
@Sendable
func proxyToUserService(req: Request) async throws -> Response {
let targetURL = URI(string: "http://user-service:8080\(req.url.path)")
return try await req.client.send(req.method, headers: req.headers, body: req.body.data, to: targetURL)
}
@Sendable
func proxyToOrderService(req: Request) async throws -> Response {
let targetURL = URI(string: "http://order-service:8080\(req.url.path)")
return try await req.client.send(req.method, headers: req.headers, body: req.body.data, to: targetURL)
}
}
Просто, правда? Vapor берёт на себя всю работу с HTTP. В реальном проекте стоит добавить таймауты, повторные попытки и circuit breaker — но для старта хватит.
Шаг 4. Агрегация данных: объединяем ответы сервисов
Самая мощная фича API Gateway — композиция данных. Допустим, клиент хочет получить профиль пользователя вместе с его последними заказами. Вместо двух запросов делаем один:
@Sendable
func aggregatedProfile(req: Request) async throws -> some Content {
// Получаем ID пользователя из JWT
guard let payload = req.storage.get(UserPayloadKey.self) else {
throw Abort(.unauthorized)
}
// Параллельные запросы к двум микросервисам
async let userResponse = req.client.get("http://user-service:8080/users/\(payload.userID)")
async let ordersResponse = req.client.get("http://order-service:8080/orders?userID=\(payload.userID)&limit=5")
let user = try await userResponse.get().content.decode(UserDTO.self)
let orders = try await ordersResponse.get().content.decode([OrderDTO].self)
// Возвращаем агрегированный ответ
return AggregatedProfile(user: user, recentOrders: orders)
}
struct AggregatedProfile: Content {
let user: UserDTO
let recentOrders: [OrderDTO]
}
Ключевой момент — async let. Зап