Skip to content

bluetape4k/bluetape4k-aws

Repository files navigation

bluetape4k-aws

CI Kotlin JVM License: MIT

English | 한국어

Bluetape4k AWS workbench

Kotlin/JVM wrappers for AWS Java SDK v2 and the AWS Kotlin SDK, with Kotlin Coroutines support, Spring Boot 4 auto-configuration, and Ktor 3 integration. Part of the bluetape4k ecosystem.


Project Purpose

bluetape4k-aws keeps AWS service access idiomatic for Kotlin services. It bridges the Java SDK v2 async model, the AWS Kotlin SDK suspend model, Spring Boot 4 auto-configuration, and Ktor 3 HTTP integration without forcing applications to adopt a single framework or dependency stack.

What It Provides

  • Kotlin-first AWS clients — coroutine adapters for Java SDK v2 plus native AWS Kotlin SDK helpers and DSL builders.
  • Service coverage — DynamoDB, S3, SES/SESv2, SNS, SQS, KMS, CloudWatch, CloudWatch Logs, Kinesis, and STS.
  • Spring Boot 4 operations — coroutine-oriented templates, repositories, listeners, and auto-configuration without awspring.
  • Ktor 3 integration — SigV4 signing, coroutine S3 client support, SQS consumer runtime, DynamoDB server repository support, and Ktor server/client examples.
  • Local integration testing — LocalStack/FLOCI emulator wiring through Testcontainers and Nightly examples.

Overview Diagram

Bluetape4k AWS overview diagram

Module Composition Chart

Bluetape4k AWS module composition chart

Modules

Module Artifact Description
bluetape4k-aws-java io.github.bluetape4k.aws:bluetape4k-aws-java AWS Java SDK v2 wrappers. Sync, async (CompletableFuture), and Coroutines extensions for DynamoDB, S3, SES/v2, SNS, SQS, KMS, CloudWatch, CloudWatch Logs, Kinesis, STS
bluetape4k-aws-kotlin io.github.bluetape4k.aws:bluetape4k-aws-kotlin AWS Kotlin SDK wrappers. Native suspend functions + DSL builders for DynamoDB, S3, SES/v2, SNS, SQS, KMS, CloudWatch, CloudWatch Logs, Kinesis, STS
bluetape4k-aws-exposed io.github.bluetape4k.aws:bluetape4k-aws-exposed Shared Exposed JDBC database foundation for AWS-backed configuration. Provides database properties, pluggable settings resolution, Hikari-backed Exposed Database creation, and default/named database registry support
bluetape4k-aws-spring-boot io.github.bluetape4k.aws:bluetape4k-aws-spring-boot Spring Boot 4 auto-configuration for AWS services. Coroutines-native, no awspring dependency. Includes S3 Transfer Manager (S3TransferTemplate), SES sender and JavaMail adapter, SNS HTTP endpoint notification parsing (SnsHttpMessageParser), SQS listener support, DynamoDB, KMS, Secrets Manager, and Parameter Store
bluetape4k-aws-ktor io.github.bluetape4k.aws:bluetape4k-aws-ktor Ktor 3 SigV4 client plugin, coroutine-friendly S3 REST client, SQS consumer runtime, and DynamoDB server repository plugin
aws-ktor-dynamodb-examples not published Ktor 3 DynamoDB server repository example backed by LocalStack/FLOCI
aws-ktor-s3-examples not published LocalStack-oriented examples for S3KtorClient; compiled and tested in Nightly
aws-ktor-sqs-examples not published Ktor 3 SQS consumer/runtime example with local emulator wiring
aws-spring-boot-dynamodb-examples not published Spring Boot 4 DynamoDB repository examples for coroutine service flows
aws-spring-boot-s3-examples not published Spring Boot 4 WebFlux examples for S3Operations/S3CoroutinesTemplate; compiled, tested, and wired for Spring AOT
aws-spring-boot-sqs-examples not published Spring Boot 4 SQS/SNS fanout examples for SqsOperations, @SqsListener, and LocalStack SNS subscriptions; compiled, tested, and wired for Spring AOT

Component Map

AWS component map diagram

Service Coverage Chart

AWS service coverage chart


Architecture

Overview

Overview diagram

Three-Tier API (bluetape4k-aws-java module — Java SDK v2)

Three-Tier API (bluetape4k-aws-java module — Java SDK v2) diagram

Native Suspend (bluetape4k-aws-kotlin module — Kotlin SDK)

Native Suspend (bluetape4k-aws-kotlin module — Kotlin SDK) diagram


Requirements

  • JDK: 21+
  • Kotlin: 2.3+
  • Gradle: 9.5+

Installation

AWS service SDKs are declared as compileOnly in this library. Add only the service dependencies you need at runtime.

Using bluetape4k-aws-java (Java SDK v2 wrappers)

dependencies {
    implementation("io.github.bluetape4k.aws:bluetape4k-aws-java:0.1.0-SNAPSHOT")

    // Add the AWS Java SDK v2 services you use
    implementation(platform("software.amazon.awssdk:bom:${awsSdkVersion}"))
    implementation("software.amazon.awssdk:dynamodb-enhanced")
    implementation("software.amazon.awssdk:s3")
    implementation("software.amazon.awssdk:s3-transfer-manager")
    implementation("software.amazon.awssdk:secretsmanager")
    implementation("software.amazon.awssdk:sqs")
    implementation("software.amazon.awssdk:ssm")
    implementation("software.amazon.awssdk:sns")
    implementation("software.amazon.awssdk:kms")
    implementation("software.amazon.awssdk:cloudwatch")
    implementation("software.amazon.awssdk:cloudwatchlogs")
    implementation("software.amazon.awssdk:kinesis")
    implementation("software.amazon.awssdk:sts")
}

For Maven Central Snapshots, add the repository:

repositories {
    maven("https://central.sonatype.com/repository/maven-snapshots/")
}

Using bluetape4k-aws-kotlin (Kotlin SDK wrappers)

dependencies {
    implementation("io.github.bluetape4k.aws:bluetape4k-aws-kotlin:0.1.0-SNAPSHOT")

    // Add the AWS Kotlin SDK services you use
    implementation("aws.sdk.kotlin:dynamodb:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:s3:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:sqs:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:sns:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:kms:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:cloudwatch:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:kinesis:${awsKotlinSdkVersion}")
    implementation("aws.sdk.kotlin:sts:${awsKotlinSdkVersion}")
}

Using bluetape4k-aws-spring-boot (Spring Boot 4 auto-configuration)

dependencies {
    implementation("io.github.bluetape4k.aws:bluetape4k-aws-spring-boot:0.1.0-SNAPSHOT")

    // Add the AWS Java SDK v2 services you use at runtime.
    implementation(platform("software.amazon.awssdk:bom:${awsSdkVersion}"))
    implementation("software.amazon.awssdk:dynamodb-enhanced")
    implementation("software.amazon.awssdk:kms")
    implementation("software.amazon.awssdk:s3")
    implementation("software.amazon.awssdk:secretsmanager")
    implementation("software.amazon.awssdk:sns")
    implementation("software.amazon.awssdk:sqs")

    // Optional: only needed if you want the Spring Security TextEncryptor adapter.
    implementation("org.springframework.security:spring-security-crypto")
    implementation("software.amazon.awssdk:ssm")
}

Use this module when your application wants Spring-managed AWS clients and coroutine-friendly service helpers. The library does not pull every AWS SDK service at runtime; add only the AWS SDK modules you actually use. For KMS, add software.amazon.awssdk:kms. Add spring-security-crypto only when you want to inject Spring Security's synchronous TextEncryptor.

bluetape4k:
  aws:
    kms:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      key-id: alias/app-secrets
      encryption-context:
        service: order-api
      data-key-cache:
        enabled: true
        max-size: 64
        ttl: PT5M
    s3:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      path-style-access-enabled: true
      presign:
        duration: PT15M
    dynamodb:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      table-prefix: local-
    sqs:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      listener:
        max-messages: 10
        wait-time-seconds: 20
        concurrency: 2
    secrets-manager:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      sources:
        - name: app-secret
          secret-id: local/app
          prefix: app
    parameter-store:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      sources:
        - name: app-parameters
          path: /config/app
          prefix: app
          recursive: true
          with-decryption: true
    sns:
      region: ap-northeast-2
      endpoint-override: http://localhost:4566
      topics:
        orders.fifo:
          fifo: true
          content-based-deduplication: true
          fifo-throughput-scope: message-group

KMS is intended for small secrets and key management, not for bulk payload encryption. Use KmsOperations.encrypt directly for short values such as tokens, credentials, or configuration secrets. For larger data, call generateDataKey, encrypt the payload locally with the plaintext data key, and store the encrypted data key with the payload metadata. The built-in DataKeyCache can reuse plaintext data keys briefly, but this is sensitive in-memory key material; keep the TTL and cache size small.

KMS Spring Boot Components

@startuml
skinparam componentStyle rectangle
skinparam shadowing false

package "Application" {
  component "Service" as Service
  component "TextEncryptor\n(optional)" as TextEncryptor
}

package "aws-spring-boot" {
  component "KmsAutoConfiguration" as Auto
  component "KmsProperties" as Props
  component "KmsOperations" as Ops
  component "KmsCoroutinesEncryptor" as Encryptor
  component "DataKeyCache" as Cache
  component "KmsTextEncryptor" as Adapter
}

package "AWS SDK v2" {
  component "KmsAsyncClient" as Client
}

cloud "AWS KMS\nor LocalStack" as Kms

Auto --> Props
Auto --> Client
Auto --> Cache
Auto --> Encryptor
Ops <|.. Encryptor
TextEncryptor <|.. Adapter
Adapter --> Ops
Service --> Ops
Service --> TextEncryptor
Encryptor --> Client
Encryptor --> Cache
Client --> Kms
@enduml

KMS Encrypt / Decrypt Flow

@startuml
skinparam shadowing false
actor App
participant "KmsOperations" as Ops
participant "KmsCoroutinesEncryptor" as Encryptor
participant "KmsAsyncClient" as Client
participant "AWS KMS" as Kms

App -> Ops: encrypt(plaintext, keyId?, context?)
Ops -> Encryptor: apply default key and context
Encryptor -> Client: Encrypt
Client -> Kms: encrypt request
Kms --> Client: ciphertextBlob
Client --> Encryptor: EncryptResponse
Encryptor --> App: ciphertext bytes

App -> Ops: decrypt(ciphertext, keyId?, context?)
Ops -> Encryptor: apply default context
Encryptor -> Client: Decrypt
Client -> Kms: decrypt request
Kms --> Client: plaintext
Client --> Encryptor: DecryptResponse
Encryptor --> App: plaintext bytes
@enduml

Usage

S3 — Spring Boot Coroutines Template

import io.bluetape4k.aws.spring.s3.S3Operations
import io.bluetape4k.aws.spring.s3.S3TransferOperations
import java.nio.file.Path

class DocumentStorage(
    private val s3: S3Operations,
    private val transfer: S3TransferOperations,
) {
    suspend fun save(bucket: String, key: String, contents: String) {
        s3.upload(bucket, key, contents, contentType = "text/plain")
    }

    suspend fun read(bucket: String, key: String): String =
        s3.downloadText(bucket, key)

    suspend fun saveLargeFile(bucket: String, key: String, source: Path) {
        transfer.uploadFile(bucket, key, source)
    }
}

S3Operations covers small/common object operations, resources, listing, and presigned URLs. S3TransferOperations is auto-configured only when software.amazon.awssdk:s3-transfer-manager is on the classpath and is intended for large files, multipart transfers, and transfer listeners. For CRT-backed throughput tuning, add the AWS CRT runtime dependency and provide a CRT-backed S3AsyncClient; the Spring auto-configuration reuses that client for transfer manager construction.

DynamoDB — Spring Boot Coroutine Repository

import io.bluetape4k.aws.spring.dynamodb.AbstractCoroutinesDynamoDbRepository
import io.bluetape4k.aws.spring.dynamodb.DynamoDbTableNameResolver
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient
import software.amazon.awssdk.enhanced.dynamodb.Key

class OrderRepository(
    enhancedClient: DynamoDbEnhancedAsyncClient,
    tableNameResolver: DynamoDbTableNameResolver,
) : AbstractCoroutinesDynamoDbRepository<OrderDocument, OrderId>(
    enhancedClient = enhancedClient,
    tableNameResolver = tableNameResolver,
    entityClass = OrderDocument::class.java,
) {
    override val tableName: String = "orders"

    override fun keyFromId(id: OrderId): Key =
        Key.builder().partitionValue(id.orderId).sortValue(id.createdAt).build()
}

aws-spring-boot does not create DynamoDB tables automatically. Create tables through migrations, deployment automation, or explicit test setup.

Secrets Manager and Parameter Store — Environment Sources

Secrets Manager and SSM Parameter Store sources are loaded during Spring Environment post-processing, before normal @ConfigurationProperties binding. No remote lookup is performed unless at least one source is configured.

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("app.db")
data class DatabaseSettings(
    val username: String,
    val password: String,
)

For a JSON secret such as {"db":{"username":"scott","password":"tiger"}} with prefix: app, the properties become app.db.username and app.db.password. For Parameter Store path /config/app/db/password with path: /config/app and prefix: app, the property becomes app.db.password.

SQS — Spring Boot Coroutines Template and Listener

import io.bluetape4k.aws.spring.sqs.SqsListener
import io.bluetape4k.aws.spring.sqs.SqsOperations

class OrderQueue(
    private val sqs: SqsOperations,
) {
    suspend fun send(queueUrl: String, payload: String) {
        sqs.send(queueUrl, payload)
    }

    @SqsListener("\${orders.queue-url}")
    suspend fun handle(body: String) {
        // Make handlers idempotent; failed messages are not deleted automatically.
        process(body)
    }
}

KMS — Spring Boot Coroutines Encryptor

import io.bluetape4k.aws.spring.kms.KmsOperations
import java.util.Base64

class SecretVault(
    private val kms: KmsOperations,
) {
    suspend fun protectToken(token: String): String {
        val ciphertext = kms.encrypt(
            plaintext = token.encodeToByteArray(),
            encryptionContext = mapOf("purpose" to "api-token"),
        )
        return Base64.getEncoder().encodeToString(ciphertext)
    }

    suspend fun revealToken(encodedCiphertext: String): String {
        val plaintext = kms.decrypt(
            ciphertext = Base64.getDecoder().decode(encodedCiphertext),
            encryptionContext = mapOf("purpose" to "api-token"),
        )
        return plaintext.decodeToString()
    }
}

The encryption context is authenticated metadata. Use the same context for decrypt that you used for encrypt, and put stable identifiers such as service, tenant, or purpose in it. Do not put secrets in the context because AWS logs and policies may expose it.

KMS — Spring Security TextEncryptor

import org.springframework.security.crypto.encrypt.TextEncryptor

class PropertyProtector(
    private val textEncryptor: TextEncryptor,
) {
    fun encrypt(value: String): String =
        textEncryptor.encrypt(value)

    fun decrypt(value: String): String =
        textEncryptor.decrypt(value)
}

TextEncryptor is synchronous, so this adapter is best for short administrative flows or startup-time secret handling. Prefer KmsOperations in coroutine services.

SNS — Spring Boot Coroutines Template

import io.bluetape4k.aws.spring.sns.SnsOperations
import io.bluetape4k.aws.spring.sns.SnsHttpMessageParser
import io.bluetape4k.aws.spring.sns.SnsHttpMessageType
import io.bluetape4k.aws.spring.sns.SnsPublishRequest
import io.bluetape4k.aws.spring.sns.SnsSmsRequest
import io.bluetape4k.aws.spring.sns.SnsSmsType

class OrderTopic(
    private val sns: SnsOperations,
) {
    suspend fun publish(topicArn: String, payload: String): String {
        val response = sns.publish(
            SnsPublishRequest(
                topicArn = topicArn,
                message = payload,
            )
        )
        return response.messageId()
    }

    suspend fun sendSms(phoneNumber: String, text: String): String =
        sns.publishSms(
            SnsSmsRequest(
                phoneNumber = phoneNumber,
                message = text,
                smsType = SnsSmsType.TRANSACTIONAL,
                senderId = "BLUETAPE",
            )
        ).messageId()

    suspend fun handleHttpEndpoint(body: String, messageTypeHeader: String?) {
        val message = SnsHttpMessageParser.parse(body, messageTypeHeader)
        // Verify Signature, SigningCertURL, SignatureVersion, and expected TopicArn here.
        when (message.type) {
            SnsHttpMessageType.SUBSCRIPTION_CONFIRMATION,
            SnsHttpMessageType.UNSUBSCRIBE_CONFIRMATION -> sns.confirmSubscription(message)
            SnsHttpMessageType.NOTIFICATION -> processNotification(message.message)
        }
    }

    private fun processNotification(message: String) = Unit
}

SNS can publish to an SQS subscription when the queue policy allows sqs:SendMessage from the topic ARN. The aws-spring-boot-sqs-examples module includes the LocalStack-oriented SQS/SNS fanout flow. SnsHttpMessageParser maps SNS HTTP JSON, checks the optional x-amz-sns-message-type header, and rejects non-HTTPS or non-SNS SigningCertURL hosts, but it does not validate SNS signatures. Validate the certificate chain, Signature, SignatureVersion, and expected TopicArn before processing notifications or confirming subscriptions.

S3 Upload — Coroutines (aws-java module)

import io.bluetape4k.aws.s3.coroutines.*
import software.amazon.awssdk.services.s3.S3AsyncClient

val s3: S3AsyncClient = S3AsyncClient.create()

suspend fun uploadObject(bucket: String, key: String, bytes: ByteArray) =
    s3.putObjectSuspend(bucket, key) {
        it.contentLength(bytes.size.toLong())
    }

SQS Send / Receive — Coroutines (aws-java module)

import io.bluetape4k.aws.sqs.coroutines.*

suspend fun sendMessage(client: SqsAsyncClient, queueUrl: String, body: String) =
    client.sendMessageSuspend {
        it.queueUrl(queueUrl).messageBody(body)
    }

suspend fun receiveMessages(client: SqsAsyncClient, queueUrl: String) =
    client.receiveMessageSuspend {
        it.queueUrl(queueUrl).maxNumberOfMessages(10)
    }.messages()

DynamoDB — Native Suspend (bluetape4k-aws-kotlin module)

import aws.sdk.kotlin.services.dynamodb.DynamoDbClient
import io.bluetape4k.aws.kotlin.dynamodb.*

// One-shot: auto-close after the block
suspend fun getItem(tableName: String, key: Map<String, AttributeValue>) =
    withDynamoDbClient(region = "ap-northeast-2") { client ->
        client.getItem {
            this.tableName = tableName
            this.key = key
        }
    }

CloudWatch Metrics — DSL (bluetape4k-aws-kotlin module)

import io.bluetape4k.aws.kotlin.cloudwatch.*
import aws.sdk.kotlin.services.cloudwatch.CloudWatchClient

val cw = CloudWatchClient { region = "ap-northeast-2" }

suspend fun publishMetric(namespace: String, value: Double) {
    cw.putMetricData {
        this.namespace = namespace
        metricData = listOf(
            metricDatum {                // bluetape4k DSL
                metricName = "RequestCount"
                this.value = value
                unit = StandardUnit.Count
            }
        )
    }
}

Test Environment

Integration tests use LocalStack (default) or Floci as a local AWS emulator, started automatically via Testcontainers.

# Run with LocalStack (default)
./gradlew :bluetape4k-aws-java:test
./gradlew :bluetape4k-aws-kotlin:test
./gradlew :bluetape4k-aws-exposed:test

# Run with Floci emulator
./gradlew :bluetape4k-aws-java:test -Dbluetape4k.aws.emulator=floci
./gradlew :bluetape4k-aws-kotlin:test -Dbluetape4k.aws.emulator=floci

License

MIT License — see LICENSE.

About

Kotlin/JVM AWS SDK v2 and AWS Kotlin SDK wrappers with Spring Boot integration — part of the bluetape4k ecosystem

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages