API Gateway для Swift-сервисов: как объединить микросервисы в единую систему

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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. Зап

Блоги

Рекомендаций по книгам