Serverless функции на Kotlin: AWS Lambda — полное руководство

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

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

Начать курс

Введение в 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"
    }
}

Минимизация размера JAR

Блоги

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