AWS SDK for Kotlin

repository·main·Indexed 19 days ago

https://github.com/aws/aws-sdk-kotlin

A collection of service clients and utilities for interacting with AWS services from Kotlin applications, supporting JVM, Android, and GraalVM targets. Built using smithy-kotlin, the SDK includes the foundational aws-core module and provides capabilities for custom endpoint resolution, alternative HTTP client configuration, and GraalVM native image support. It also features a DynamoDB Mapper with Key Projections and an HLL Converters framework for bidirectional data mapping.

Tokens
11.9K
Snippets
34
Records
53
Agent score
67%

What's inside aws-sdk-kotlin

  1. Overview of AWS SDK for Kotlin design principles

    main
    The AWS SDK for Kotlin is built using smithy-kotlin. Its architecture and implementation are guided by specific design tenets and are informed by both Smithy Kotlin designs and SDK-specific design documents. Developers building with the SDK should be aware that its behavior (such as retry logic) may extend or augment the base Smithy Kotlin implementations.
  2. How the AWS SDK for Kotlin handles retries

    main

    The AWS SDK for Kotlin implements a specialized retry mechanism based on the smithy-kotlin design. It uses a custom AwsDefaultRetryPolicy that extends the standard retry policy to handle AWS-specific error codes.

    When an AwsServiceException is encountered, the SDK inspects the errorCode within the exception's sdkErrorMetadata. It maps these specific error codes to retry directives (such as Throttling or Timeout) to determine if a request should be retried. This allows the SDK to react intelligently to service-specific throttling and timeout errors that a generic retry policy might not recognize.

  3. Dependency rules for aws-core

    main

    The aws-core module follows a specific dependency hierarchy within the SDK:

    • Downstream usage: Other SDK modules are permitted to depend on aws-core.
    • Constraint: The aws-client-rt module must not depend on any other SDK modules, ensuring a clean separation of concerns and preventing circular dependencies.
  4. Manage streaming responses using scoped blocks

    main

    S3 streaming responses are scoped to a lambda block to simplify lifetime management. Instead of the API returning the response object directly, you must provide a lambda that receives the response.

    Critical Rule: The response and its underlying stream are only valid within the scope of the provided lambda. Do not attempt to store the response object or process the stream after the block returns.

    To use this pattern, pass a lambda to methods like getObject and perform your stream processing (e.g., writing to a file) inside that block.

    val s3 = S3Client { ... }
    val req = GetObjectRequest { ... }
    val path = Paths.get("/tmp/download.txt")
    
    val contentSize = s3.getObject(req) { resp ->
        // resp is valid only until the end of this block
        val rc = resp.body?.writeToFile(path)
        rc
    }
    println("wrote $contentSize bytes to $path")
  5. Handle binary data with ByteStream in S3

    main

    In the S3 module, binary data and streams are represented using the ByteStream type.

    Supplying data to S3

    You can provide a ByteStream to requests (such as PutObjectRequest) using several convenience functions:

    • ByteStream.fromFile(file): From a file.
    • ByteStream.fromBytes(byteArray): From a byte array.
    • ByteStream.fromString("string"): From a string.

    Consuming data from S3

    When receiving data from S3, the response body is a ByteStream. You can consume it using methods like:

    • resp.body?.writeToFile(path): Writes the stream to a file.
    • resp.body?.toByteArray(): Warning: This buffers the entire stream in-memory.
    • resp.body?.decodeToString(): Warning: This buffers the entire stream in-memory and converts it to a string.
    val req = PutObjectRequest {
        ... 
        body = ByteStream.fromFile(file)
    }
    
    s3.getObject(req) { resp ->
        // resp.body is a ByteStream instance
        resp.body?.writeToFile(path)
    }
  6. Understand the AWS SDK for Kotlin versioning scheme

    main

    The AWS SDK for Kotlin uses a MAJOR.MINOR.PATCH[-QUALIFIER] versioning format. Unlike strict semantic versioning (semver), PATCH releases may include new features in addition to bug fixes to accommodate frequent updates to AWS service models. The version components indicate the risk level of an upgrade:

    • MAJOR: High risk. Expect API incompatibilities and breaking changes.
    • MINOR: Medium risk. Upgrading should usually work, but check release notes. Changes may include incrementing the Kotlin version, deprecating APIs, or significant core runtime changes. In some scenarios, MINOR updates may contain backwards incompatible changes.
    • PATCH: Low risk. Contains bug fixes and new features; generally safe to consume.
    • QUALIFIER: (Optional) Indicates pre-release status (e.g., alpha, beta, rc-1).
  7. How HLL Converters work

    main

    The HLL Converters framework provides a system for bidirectional data mapping between two types. The framework uses two primary concepts to distinguish between types:

    • Left (L): The type closer to your application or business logic (often referred to as "your" types).
    • Right (R): The type farther away from your application or business logic (often referred to as "their" types).

    A converter performs two operations:

    1. convertRight: Maps from a Left type to a Right type.
    2. convertLeft: Maps from a Right type to a Left type.
  8. Understand Key Projections in DynamoDB Mapper

    main

    The DynamoDB Mapper uses a technique called Key Projections to transform untyped low-level DynamoDB types (like Map<String, AttributeValue>) into type-safe high-level domain types.

    Because low-level DynamoDB operations often use maps to represent item keys, the Mapper generates specialized type variants that model partition keys and sort keys as distinct, typed fields. This ensures that high-level requests and responses correctly represent the key structures required by DynamoDB operations.

  9. Identify and avoid Internal APIs

    main

    Any API annotated with @InternalSdkApi or @InternalApi is considered an internal implementation detail. These APIs:

    • Are not subject to backwards compatibility guarantees.
    • May be changed or removed without notice.
    • Are intended for use only by the SDK itself.

    Changes to these APIs may trigger a MINOR version bump.

  10. Understand the difference between `endpointUrl` and `EndpointProvider`

    main

    The two configuration options behave differently:

    1. endpointUrl: Sets a base URL that is passed to the provider. The provider can still modify it (e.g., adding a bucket name to the host).
    2. endpointProvider: Replaces the resolution logic entirely. The provider returns an absolute endpoint that is used without further modification by the SDK.

    In most use cases, you only need to modify endpointUrl.

    // using endpointUrl: The provider can still modify this base URL
    S3Client.fromEnvironment { endpointUrl = Url.parse("https://endpoint.example") }
    
    // using endpointProvider: The provider returns the final, absolute endpoint
    S3Client.fromEnvironment {
        endpointProvider = object : EndpointProvider {
            override suspend fun resolveEndpoint(params: EndpointParameters): Endpoint = Endpoint("https://endpoint.example")
        }
    }
  11. Understand component and Kotlin versioning

    main

    Component Versioning

    The SDK versions all service clients (e.g., S3, EC2, DynamoDb) and the core runtime (e.g., aws-config) together under a single version. This ensures that all clients and the runtime remain compatible when you upgrade.

    Kotlin Version Support

    The SDK supports the latest available version of Kotlin. When the SDK adopts a new Kotlin version, it will increment the MINOR version. While the SDK may work with older Kotlin versions, no support or guarantees are provided for them. Note that upgrading the Kotlin version the SDK is compiled with does not automatically mean all new language features are immediately adopted by the SDK.