API Gateway для микросервисов на Kotlin: когда один вход лучше тысячи

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

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

Начать курс

API Gateway для Kotlin сервисов: когда один вход лучше тысячи

Представьте, что ваш микросервисный зоопарк на Kotlin разросся до 20+ сервисов. Каждый из них стучится в мир через свой порт, требует свою аутентификацию и по-своему обрабатывает ошибки. Клиенты путаются в эндпоинтах, а вы каждую ночь молитесь, чтобы лишний сервис не упал и не похоронил под собой всё приложение. Знакомо?

Выход есть — API Gateway. Это единая точка входа, которая берёт на себя маршрутизацию, безопасность, кэширование и мониторинг. Сегодня мы разберём, как построить такой шлюз на Kotlin, используя два популярных подхода: лёгкий Ktor и промышленный Spring Cloud Gateway. Никакой воды — только код и практика.

Что такое API Gateway и зачем он вам нужен?

API Gateway (шлюз API) — это прослойка между клиентами и вашими микросервисами. Вместо того чтобы каждый фронтенд или внешнее приложение знало адреса всех ваших сервисов, они общаются только с шлюзом. А он уже решает, куда направить запрос.

Основные функции шлюза:

  • Маршрутизация — направляет запросы к нужным сервисам на основе URL или заголовков.
  • Аутентификация и авторизация — проверяет токены до того, как запрос попадёт в бизнес-логику.
  • Балансировка нагрузки — распределяет запросы между инстансами одного сервиса.
  • Кэширование — сохраняет ответы для часто запрашиваемых данных.
  • Мониторинг и логирование — собирает метрики по всем запросам в одном месте.
  • Rate limiting — защищает от DDoS и перегрузок.

Если вы пишете на Kotlin и используете микросервисную архитектуру, API Gateway — это must-have, а не опция. Без него вы рискуете получить хаос из разрозненных эндпоинтов и проблем с безопасностью.

Подход 1: API Gateway на Ktor — лёгкость и гибкость

Ktor — это фреймворк от JetBrains, созданный специально для Kotlin. Он асинхронный, легковесный и идеально подходит для написания шлюзов, когда не хочется тащить тяжёлый Spring Boot.

Давайте напишем простой API Gateway на Ktor, который маршрутизирует запросы к двум сервисам: user-service (пользователи) и order-service (заказы).

Шаг 1: Настройка проекта

Создайте новый проект Ktor через start.ktor.io или добавьте зависимости вручную в build.gradle.kts:

dependencies {
    implementation("io.ktor:ktor-server-core:2.3.7")
    implementation("io.ktor:ktor-server-netty:2.3.7")
    implementation("io.ktor:ktor-client-core:2.3.7")
    implementation("io.ktor:ktor-client-cio:2.3.7")
    implementation("io.ktor:ktor-server-auth:2.3.7")
    implementation("io.ktor:ktor-server-content-negotiation:2.3.7")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
    implementation("ch.qos.logback:logback-classic:1.4.14")
}

Шаг 2: Базовая маршрутизация с проксированием

Создадим главный файл Application.kt:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main() {
    embeddedServer(Netty, port = 8080) {
        module()
    }.start(wait = true)
}

fun Application.module() {
    val httpClient = HttpClient()

    routing {
        // Прокси для user-service
        route("/api/users") {
            handle {
                val response = httpClient.get("http://localhost:8081${call.request.uri}")
                call.respondText(
                    text = response.bodyAsText(),
                    contentType = ContentType.Application.Json,
                    status = response.status
                )
            }
        }

        // Прокси для order-service
        route("/api/orders") {
            handle {
                val response = httpClient.get("http://localhost:8082${call.request.uri}")
                call.respondText(
                    text = response.bodyAsText(),
                    contentType = ContentType.Application.Json,
                    status = response.status
                )
            }
        }

        // Health check
        get("/health") {
            call.respondText("Gateway is alive!", ContentType.Text.Plain)
        }
    }
}

Этот код принимает запросы на порту 8080 и перенаправляет их на соответствующие сервисы. Просто, но уже работает.

Шаг 3: Добавляем аутентификацию через JWT

Добавим проверку JWT-токена перед проксированием. Для этого используем плагин аутентификации Ktor:

import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.module() {
    // ... httpClient и routing

    install(Authentication) {
        jwt("auth-jwt") {
            verifier(JWTVerifier)
            realm = "Ktor Gateway"
            validate { credential ->
                if (credential.payload.getClaim("role").asString() != null) {
                    JWTPrincipal(credential.payload)
                } else null
            }
        }
    }

    routing {
        authenticate("auth-jwt") {
            route("/api/users") {
                handle { /* прокси */ }
            }
            route("/api/orders") {
                handle { /* прокси */ }
            }
        }
        get("/health") { /* без аутентификации */ }
    }
}

Теперь все запросы к защищённым маршрутам проходят проверку токена. Если токен невалидный — шлюз вернёт 401, не нагружая бэкенд-сервисы.

Подход 2: Spring Cloud Gateway — промышленный стандарт

Если ваш проект уже на Spring Boot, использовать Spring Cloud Gateway — логичный выбор. Он предоставляет декларативную конфигурацию через YAML и мощные фильтры.

Шаг 1: Зависимости и настройка

Добавьте в build.gradle.kts:

dependencies {
    implementation("org.springframework.cloud:spring-cloud-starter-gateway:4.1.0")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("io.jsonwebtoken:jjwt-api:0.12.5")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")
}

Шаг 2: Конфигурация маршрутов в YAML

Создайте application.yml:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8081
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1                

Блоги

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