Serverless функции на Kotlin: AWS Lambda от А до Я

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

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

Начать курс

Введение в serverless функции на Kotlin и AWS Lambda

Serverless-архитектура стала стандартом для создания масштабируемых и экономичных приложений. AWS Lambda — один из самых популярных сервисов для запуска кода без управления серверами. Однако большинство примеров и документации ориентированы на Java или Python. Kotlin, будучи современным языком для JVM, предлагает лаконичный синтаксис и полную совместимость с экосистемой Java, что делает его отличным выбором для Lambda-функций.

В этой статье мы разберём полный цикл: от настройки проекта до деплоя и мониторинга. Вы узнаете, как писать эффективные serverless функции на Kotlin, обрабатывать события API Gateway, S3 и DynamoDB, а также оптимизировать холодный старт.

1. Настройка проекта и зависимости

Для начала создадим Gradle-проект с поддержкой Kotlin. Основная зависимость — com.amazonaws:aws-lambda-java-core, которая предоставляет интерфейсы для хендлеров.

Шаг 1.1: Структура build.gradle.kts

plugins {
    kotlin("jvm") version "1.9.22"
    id("io.github.microutils:kotlin-logging") version "3.0.5"
}

dependencies {
    implementation("com.amazonaws:aws-lambda-java-core:1.2.2")
    implementation("com.amazonaws:aws-lambda-java-events:3.11.3")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.3")
    implementation("org.slf4j:slf4j-simple:2.0.9")
}

tasks.register<Zip>("buildLambda") {
    from(sourceSets.main.get().output)
    from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
    into("build/libs")
    archiveFileName.set("lambda.zip")
}

Обратите внимание на задачу buildLambda — она создаёт «толстый» JAR (fat jar), который AWS Lambda сможет исполнить. Без этого шага деплой не сработает.

Шаг 1.2: Базовая функция-хендлер

Создадим файл Handler.kt с простейшей функцией, которая принимает Map<String, String> и возвращает строку:

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"
        context.logger.log("Processing request for $name")
        return "Hello, $name!"
    }
}

Интерфейс RequestHandler параметризован двумя типами: тип входных данных и тип возвращаемого значения. Kotlin позволяет использовать Map, String, а также любые data-классы.

2. Работа с событиями AWS (API Gateway, S3, DynamoDB)

Serverless-функции редко работают изолированно. Чаще всего они реагируют на события от других сервисов AWS. Рассмотрим три популярных сценария.

2.1 Обработка HTTP-запросов через API Gateway

Для интеграции с API Gateway используйте класс APIGatewayProxyRequestEvent из aws-lambda-java-events:

import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler

class ApiHandler : RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    override fun handleRequest(input: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent {
        val body = "Received: ${input.body}"
        return APIGatewayProxyResponseEvent()
            .withStatusCode(200)
            .withBody(body)
            .withHeaders(mapOf("Content-Type" to "text/plain"))
    }
}

В этом примере мы извлекаем тело запроса и возвращаем HTTP-ответ. Обратите внимание: APIGatewayProxyResponseEvent позволяет задать заголовки, статус-код и тело.

2.2 Реакция на загрузку файла в S3

События S3 передаются через S3Event. Типичная задача — извлечь метаданные загруженного объекта:

import com.amazonaws.services.lambda.runtime.events.S3Event
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler

class S3EventHandler : RequestHandler<S3Event, String> {
    override fun handleRequest(event: S3Event, context: Context): String {
        event.records.forEach { record ->
            val bucket = record.s3.bucket.name
            val key = record.s3.`object`.key
            context.logger.log("File $key uploaded to $bucket")
        }
        return "Processed ${event.records.size} records"
    }
}

Обратите внимание на обратные кавычки вокруг object — это необходимо, так как object является зарезервированным словом в Kotlin.

2.3 Запись в DynamoDB

Для работы с DynamoDB используйте AWS SDK v2 для Kotlin. Добавьте зависимость:

implementation("software.amazon.awssdk:dynamodb:2.21.29")

Пример функции, которая сохраняет данные из события:

import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler

class DynamoDbHandler : RequestHandler<Map<String, String>, String> {
    private val dynamoDb = DynamoDbClient.builder().build()

    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["data"]).build()
        )
        dynamoDb.putItem(PutItemRequest.builder()
            .tableName("MyTable")
            .item(item)
            .build())
        return "Saved"
    }
}

Важно: Клиент DynamoDbClient лучше создавать один раз на уровне класса (как показано выше), чтобы избежать повторного создания подключения при каждом вызове. Это снижает холодный старт.

3. Деплой и тестирование

После написания кода необходимо упаковать и загрузить функцию в AWS. Рассмотрим два способа: через AWS Console и через AWS CLI

Блоги

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