Перейти к содержанию
V1 V2

AWS S3 клиент

Интеграция s3-client-aws не зависит от экспериментального S3-клиента Kora. Подключайте любой из артефактов отдельно или оба вместе, если нужны генерируемые операции с объектами и полный API AWS SDK.

Клиент AWS SDK

Артефакт s3-client-aws — это тонкая интеграция вокруг AWS SDK for Java v2. Он публикует собственный клиент SDK software.amazon.awssdk.services.s3.S3Client как компонент графа, настраивает его из конфигурации Kora и направляет его HTTP-трафик через HttpClient Kora, чтобы таймауты и телеметрия оставались согласованными с остальным приложением.

В нём нет аннотаций @S3 и нет моделей Kora S3 — работа с этим артефактом означает работу с API AWS SDK. Используйте его для всего, что не покрывает декларативный контракт: администрирование бакетов, копирование объектов, предподписанные URL, управление ACL и политиками, пакетное удаление.

Подключение

Зависимость build.gradle:

implementation "io.koraframework:s3-client-aws"

Модуль:

@KoraApp
public interface Application extends AwsS3ClientModule { }

Зависимость build.gradle.kts:

implementation("io.koraframework:s3-client-aws")

Модуль:

@KoraApp
interface Application : AwsS3ClientModule

Обратите внимание, что группа здесь io.koraframework, а не io.koraframework.experimental — этот артефакт не является экспериментальным модулем. Требуется добавить любой модуль HTTP-клиента: собственные транспорты SDK apache-client и netty-nio-client исключены, а Kora предоставляет реализацию SdkHttpClient поверх HttpClient из графа.

Конфигурация

Конфигурация читается по пути s3client.aws. Основные параметры:

s3client.aws {
    url = "http://localhost:9000" //(1)!
    credentials {
        accessKey = "someKey" //(2)!
        secretKey = "someSecret" //(3)!
    }
    region = "aws-global" //(4)!
}
  1. URL хранилища S3 (обязательный, по умолчанию не указано)
  2. Ключ доступа к S3 (обязательный, по умолчанию не указано)
  3. Секрет доступа к S3 (обязательный, по умолчанию не указано)
  4. Регион хранилища S3 (по умолчанию: aws-global)
s3client:
  aws:
    url: "http://localhost:9000" #(1)!
    credentials:
      accessKey: "someKey" #(2)!
      secretKey: "someSecret" #(3)!
    region: "aws-global" #(4)!
  1. URL хранилища S3 (обязательный, по умолчанию не указано)
  2. Ключ доступа к S3 (обязательный, по умолчанию не указано)
  3. Секрет доступа к S3 (обязательный, по умолчанию не указано)
  4. Регион хранилища S3 (по умолчанию: aws-global)
Полная конфигурация

Полная конфигурация описана в классе AwsS3Config (указаны примеры значений или значения по умолчанию):

s3client.aws {
    url = "http://localhost:9000" //(1)!
    credentials {
        accessKey = "someKey" //(2)!
        secretKey = "someSecret" //(3)!
    }
    region = "aws-global" //(4)!
    addressStyle = "PATH" //(5)!
    requestTimeout = "45s" //(6)!
    checksumCalculationRequest = "WHEN_REQUIRED" //(7)!
    checksumValidationResponse = "WHEN_REQUIRED" //(8)!
    chunkedEncodingEnabled = true //(9)!
    telemetry {
        logging {
            enabled = false //(10)!
        }
        metrics {
            enabled = false //(11)!
            slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(12)!
            tags = { // (13)!
                "key1" = "value1"
                "key2" = "value2"
            }
        }
        tracing {
            enabled = true //(14)!
            attributes = { // (15)!
                "key1" = "value1"
                "key2" = "value2"
            }
        }
    }
}
  1. URL хранилища S3, передаётся в SDK как переопределение endpoint (обязательный, по умолчанию не указано)
  2. Ключ доступа к S3 (обязательный, по умолчанию не указано)
  3. Секрет доступа к S3 (обязательный, по умолчанию не указано)
  4. Регион хранилища S3 (по умолчанию: aws-global)
  5. Стиль доступа к объектам, может иметь значения PATH или VIRTUAL_HOSTED (по умолчанию: PATH)
  6. Максимальное время выполнения операции, применяется к нижележащему HTTP-запросу (по умолчанию: 45s)
  7. Когда вычисляются контрольные суммы запроса, может иметь значения WHEN_SUPPORTED или WHEN_REQUIRED (по умолчанию: WHEN_REQUIRED)
  8. Когда проверяются контрольные суммы ответа, может иметь значения WHEN_SUPPORTED или WHEN_REQUIRED (по умолчанию: WHEN_REQUIRED)
  9. Использовать ли частичное (chunked) кодирование при подписании данных объекта во время загрузки (по умолчанию: true)
  10. Включает логирование модуля (по умолчанию: false)
  11. Включает метрики модуля (по умолчанию: false)
  12. Настройка SLO для метрик (по умолчанию: io.koraframework.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO)
  13. Настройка тегов метрик (по умолчанию: {})
  14. Включает трассировку модуля (по умолчанию: true)
  15. Настройка атрибутов трассировки (по умолчанию: {})
s3client:
  aws:
    url: "http://localhost:9000" #(1)!
    credentials:
      accessKey: "someKey" #(2)!
      secretKey: "someSecret" #(3)!
    region: "aws-global" #(4)!
    addressStyle: "PATH" #(5)!
    requestTimeout: "45s" #(6)!
    checksumCalculationRequest: "WHEN_REQUIRED" #(7)!
    checksumValidationResponse: "WHEN_REQUIRED" #(8)!
    chunkedEncodingEnabled: true #(9)!
    telemetry:
      logging:
        enabled: false #(10)!
      metrics:
        enabled: false #(11)!
        slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(12)!
        tags: #(13)!
          key1: value1
          key2: value2
      tracing:
        enabled: true #(14)!
        attributes: #(15)!
          key1: value1
          key2: value2
  1. URL хранилища S3, передаётся в SDK как переопределение endpoint (обязательный, по умолчанию не указано)
  2. Ключ доступа к S3 (обязательный, по умолчанию не указано)
  3. Секрет доступа к S3 (обязательный, по умолчанию не указано)
  4. Регион хранилища S3 (по умолчанию: aws-global)
  5. Стиль доступа к объектам, может иметь значения PATH или VIRTUAL_HOSTED (по умолчанию: PATH)
  6. Максимальное время выполнения операции, применяется к нижележащему HTTP-запросу (по умолчанию: 45s)
  7. Когда вычисляются контрольные суммы запроса, может иметь значения WHEN_SUPPORTED или WHEN_REQUIRED (по умолчанию: WHEN_REQUIRED)
  8. Когда проверяются контрольные суммы ответа, может иметь значения WHEN_SUPPORTED или WHEN_REQUIRED (по умолчанию: WHEN_REQUIRED)
  9. Использовать ли частичное (chunked) кодирование при подписании данных объекта во время загрузки (по умолчанию: true)
  10. Включает логирование модуля (по умолчанию: false)
  11. Включает метрики модуля (по умолчанию: false)
  12. Настройка SLO для метрик (по умолчанию: io.koraframework.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO)
  13. Настройка тегов метрик (по умолчанию: {})
  14. Включает трассировку модуля (по умолчанию: true)
  15. Настройка атрибутов трассировки (по умолчанию: {})

Имя бакета не входит в AwsS3Config: SDK принимает его в каждом запросе, поэтому объявите его в собственном интерфейсе @ConfigSource.

Использование

Клиент SDK внедряется напрямую, без тега:

@Component
public final class AwsS3Service {

    private final S3Client s3Client; //(1)!
    private final String bucket;

    public AwsS3Service(S3Client s3Client, S3Config config) {
        this.s3Client = s3Client;
        this.bucket = config.bucket();
    }

    public PutObjectResponse putObject(String key, byte[] value) {
        return s3Client.putObject(r -> r.bucket(bucket).key(key), RequestBody.fromBytes(value));
    }

    public ResponseInputStream<GetObjectResponse> getObject(String key) {
        return s3Client.getObject(r -> r.bucket(bucket).key(key));
    }

    public ListObjectsV2Response listObjects(String prefix) {
        return s3Client.listObjectsV2(r -> r.bucket(bucket).prefix(prefix).maxKeys(50));
    }

    public DeleteObjectsResponse deleteObjects(List<String> keys) { //(2)!
        var identifiers = keys.stream()
                .map(key -> ObjectIdentifier.builder().key(key).build())
                .toList();

        return s3Client.deleteObjects(r -> r.bucket(bucket).delete(d -> d.objects(identifiers)));
    }
}
  1. software.amazon.awssdk.services.s3.S3Client, клиент AWS SDK
  2. Пакетное удаление, которое декларативный клиент не генерирует
@Component
class AwsS3Service(
    private val s3Client: S3Client, //(1)!
    config: S3Config
) {

    private val bucket: String = config.bucket()

    fun putObject(key: String, value: ByteArray): PutObjectResponse =
        s3Client.putObject({ it.bucket(bucket).key(key) }, RequestBody.fromBytes(value))

    fun getObject(key: String): ResponseInputStream<GetObjectResponse> =
        s3Client.getObject { it.bucket(bucket).key(key) }

    fun listObjects(prefix: String): ListObjectsV2Response =
        s3Client.listObjectsV2 { it.bucket(bucket).prefix(prefix).maxKeys(50) }

    fun deleteObjects(keys: List<String>): DeleteObjectsResponse { //(2)!
        val identifiers = keys.map { ObjectIdentifier.builder().key(it).build() }
        return s3Client.deleteObjects { r -> r.bucket(bucket).delete { d -> d.objects(identifiers) } }
    }
}
  1. software.amazon.awssdk.services.s3.S3Client, клиент AWS SDK
  2. Пакетное удаление, которое декларативный клиент не генерирует

Об ошибках сообщает собственная иерархия исключений SDK — NoSuchKeyException, NoSuchBucketException, S3Exception — а не исключения Kora S3.

Пользовательское поведение SDK добавляется компонентами ExecutionInterceptor: каждый software.amazon.awssdk.core.interceptor.ExecutionInterceptor, опубликованный под @Tag(Tag.Factory.class), регистрируется на клиенте рядом с собственным перехватчиком телеметрии Kora.

Использование обоих артефактов

Оба артефакта могут жить в одном приложении: они занимают разные пакеты и разные секции конфигурации, поэтому согласовывать нечего.

implementation "io.koraframework:s3-client-aws"
implementation "io.koraframework.experimental:s3-client-kora"
@KoraApp
public interface Application extends AwsS3ClientModule, KoraS3ClientModule { }
implementation("io.koraframework:s3-client-aws")
implementation("io.koraframework.experimental:s3-client-kora")
@KoraApp
interface Application : AwsS3ClientModule, KoraS3ClientModule
s3client.aws { //(1)!
    url = ${S3_URL}
    credentials {
        accessKey = ${S3_ACCESS_KEY}
        secretKey = ${S3_SECRET_KEY}
    }
}

s3client.uploads { //(2)!
    endpoint = ${S3_URL}
    bucket = "uploads"
    credentials {
        accessKey = ${S3_ACCESS_KEY}
        secretKey = ${S3_SECRET_KEY}
    }
}
  1. Фиксированный путь клиента AWS SDK
  2. Путь, объявленный в @S3.Client("s3client.uploads") декларативного клиента
s3client:
  aws: #(1)!
    url: ${S3_URL}
    credentials:
      accessKey: ${S3_ACCESS_KEY}
      secretKey: ${S3_SECRET_KEY}

  uploads: #(2)!
    endpoint: ${S3_URL}
    bucket: "uploads"
    credentials:
      accessKey: ${S3_ACCESS_KEY}
      secretKey: ${S3_SECRET_KEY}
  1. Фиксированный путь клиента AWS SDK
  2. Путь, объявленный в @S3.Client("s3client.uploads") декларативного клиента

Администрирование бакетов

Создание бакета или проверка его существования не входят в контракт @S3, поэтому выполняются через клиент AWS SDK. Имя бакета из @S3.Bucket попадает в сгенерированный класс, а не в компонент, поэтому инициализатор читает тот же путь конфигурации самостоятельно:

@ConfigSource("s3client.uploads")
public interface S3UploadsConfig {

    String bucket();
}
@Root //(1)!
@Component
public final class S3BucketInitializer implements Lifecycle {

    private final S3Client s3Client; //(2)!
    private final S3UploadsConfig config;

    public S3BucketInitializer(S3Client s3Client, S3UploadsConfig config) {
        this.s3Client = s3Client;
        this.config = config;
    }

    @Override
    public void init() {
        var bucket = this.config.bucket();
        try {
            this.s3Client.headBucket(HeadBucketRequest.builder().bucket(bucket).build());
        } catch (NoSuchBucketException e) {
            this.s3Client.createBucket(CreateBucketRequest.builder().bucket(bucket).build());
        }
    }

    @Override
    public void release() {}
}
  1. Обязательно: от этого компонента никто не зависит, поэтому без @Root он будет выброшен из графа
  2. software.amazon.awssdk.services.s3.S3Client
@ConfigSource("s3client.uploads")
interface S3UploadsConfig {

    fun bucket(): String
}
@Root //(1)!
@Component
class S3BucketInitializer(
    private val s3Client: S3Client, //(2)!
    private val config: S3UploadsConfig
) : Lifecycle {

    override fun init() {
        val bucket = config.bucket()
        try {
            s3Client.headBucket(HeadBucketRequest.builder().bucket(bucket).build())
        } catch (e: NoSuchBucketException) {
            s3Client.createBucket(CreateBucketRequest.builder().bucket(bucket).build())
        }
    }

    override fun release() {}
}
  1. Обязательно: от этого компонента никто не зависит, поэтому без @Root он будет выброшен из графа
  2. software.amazon.awssdk.services.s3.S3Client

Компонент Lifecycle, от которого никто не зависит, выбрасывается

Kora собирает только достижимую часть графа. Компонент, который лишь подготавливает внешнее состояние и ни от чего не является зависимостью, удаляется вместе со всем, что он за собой тянул, и это проявляется как ошибка сборки вида interface software.amazon.awssdk.services.s3.S3Client wasn't found in graph. Пометьте его как @Root, чтобы он остался.