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