Введение в serverless вычисления на Kotlin
Serverless архитектура стала стандартом для создания масштабируемых и экономичных приложений. AWS Lambda — один из самых популярных сервисов, который позволяет запускать код без управления серверами. Хотя чаще всего Lambda ассоциируется с Node.js или Python, язык Kotlin благодаря своей лаконичности, безопасности типов и полной совместимости с Java экосистемой становится отличным выбором для написания serverless функций.
В этой статье мы подробно разберём, как создавать, тестировать и развёртывать serverless функции на Kotlin для AWS Lambda. Вы узнаете, как настроить проект, обрабатывать события API Gateway, S3 и DynamoDB, а также оптимизировать производительность.
Настройка проекта Kotlin для AWS Lambda
Для начала работы нам потребуется Gradle проект с правильными зависимостями. Основное преимущество Kotlin — возможность использовать все библиотеки Java, включая официальный AWS SDK.
Создание Gradle проекта
Создайте файл build.gradle.kts со следующей конфигурацией:
plugins {
kotlin("jvm") version "1.9.22"
id("io.github.deweyjose.lambda-packager") version "0.1.2"
}
dependencies {
implementation("com.amazonaws:aws-lambda-java-core:1.2.3")
implementation("com.amazonaws:aws-lambda-java-events:3.11.4")
implementation("com.google.code.gson:gson:2.10.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testImplementation("org.mockito:mockito-core:5.11.0")
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}
Структура handler-функции
Любая Lambda функция на Kotlin должна реализовывать интерфейс RequestHandler или RequestStreamHandler. Рассмотрим простейший пример HelloHandler:
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
class HelloHandler : RequestHandler<Map<String, String>, String> {
override fun handleRequest(input: Map<String, String>, context: Context): String {
val name = input["name"] ?: "World"
return "Hello, $name! Привет из Kotlin Lambda!"
}
}
Обратите внимание: Kotlin позволяет использовать Map<String, String> для работы с JSON-событиями, но для сложных структур лучше создать Data Class.
Работа с различными триггерами AWS
Serverless функции редко работают изолированно. Рассмотрим три популярных сценария: API Gateway, S3 и DynamoDB.
API Gateway + Lambda на Kotlin
Для обработки HTTP-запросов используем класс APIGatewayProxyRequestEvent:
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
class ApiHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
override fun handleRequest(event: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent {
val body = """
{"message": "Получен ${event.httpMethod} запрос к ${event.path}",
"queryParams": "${event.queryStringParameters}"}
""".trimIndent()
return APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(mapOf("Content-Type" to "application/json"))
.withBody(body)
}
}
Обработка событий S3
Когда в S3 bucket загружается новый объект, Lambda может автоматически запуститься:
import com.amazonaws.services.lambda.runtime.events.S3Event
import com.amazonaws.services.s3.AmazonS3ClientBuilder
class S3ProcessorHandler : RequestHandler<S3Event, String> {
private val s3Client = AmazonS3ClientBuilder.defaultClient()
override fun handleRequest(event: S3Event, context: Context): String {
event.records.forEach { record ->
val bucket = record.s3.bucket.name
val key = record.s3.`object`.key
val content = s3Client.getObjectAsString(bucket, key)
context.logger.log("Обработан файл: $key, размер: ${content.length} символов")
}
return "Обработано ${event.records.size} файлов"
}
}
Обратите внимание на использование обратных кавычек для object — это ключевое слово в Kotlin.
Интеграция с DynamoDB
Для работы с DynamoDB используем AWS SDK v2 (рекомендуется для новых проектов):
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
class DynamoDbHandler : RequestHandler<Map<String, String>, String> {
private val dynamoDb = DynamoDbClient.create()
override fun handleRequest(input: Map<String, String>, context: Context): String {
val item = mapOf(
"id" to AttributeValue.builder().s(input["id"]).build(),
"data" to AttributeValue.builder().s(input["payload"]).build()
)
dynamoDb.putItem { it.tableName("MyTable").item(item) }
return "Данные сохранены в DynamoDB"
}
}
Оптимизация холодного старта и производительности
Одна из главных проблем Kotlin на Lambda — холодный старт. Вот проверенные методы оптимизации:
Использование SnapStart
Сервис AWS Lambda SnapStart позволяет делать снапшоты инициализированного окружения. Для Kotlin это особенно полезно, так как JVM стартует дольше Python или Node.js:
// В build.gradle.kts добавляем плагин
plugins {
kotlin("jvm") version "1.9.22"
id("io.github.deweyjose.lambda-packager") version "0.1.2"
id("com.github.johnrengelman.shadow") version "8.1.1" // для fat JAR
}
// В AWS Console включаем SnapStart для функции
Ленивая инициализация клиентов
Создавайте AWS-клиенты один раз и переиспользуйте их между вызовами (это делается автоматически, если клиент объявлен как свойство класса):
class OptimizedHandler : RequestHandler<Map<String, String>, String> {
// Клиент создаётся один раз при первом холодном старте
private val s3Client by lazy { AmazonS3ClientBuilder.defaultClient() }
override fun handleRequest(input: Map<String, String>, context: Context): String {
// Используем s3Client многократно
return "OK"
}
}