Код, говорящий с AWS, выглядит одинаково независимо от того, где он запущен — EC2, EKS или ноутбук разработчика. Достигается это тремя вещами: SDK v2 с его единообразными клиентами, credentials chain (код не знает, откуда берутся права) и LocalStack в тестах. Разберём все три плюс Spring Cloud AWS — обвязку, избавляющую от шаблонного кода.
SDK v2: клиенты как бины
Официальный SDK — software.amazon.awssdk:* (v2; v1 на com.amazonaws — устаревшее поколение, в новом коде ему делать нечего). Один клиент на сервис AWS, потокобезопасный, переиспользуемый — то есть бин:
@Configuration
public class AwsConfig {
@Bean
SqsClient sqsClient() {
return SqsClient.builder()
.region(Region.EU_CENTRAL_1)
.build();
}
@Bean
S3Client s3Client() {
return S3Client.builder()
.region(Region.EU_CENTRAL_1)
.build();
}
}
Знакомые мотивы: builder-конфигурация, явные таймауты через overrideConfiguration (дефолты SDK разумны, но resilience-дисциплина требует их знать), встроенные ретраи с backoff — которые надо учитывать, проектируя свои: ретрай поверх ретрая SDK — та же лавина, что и ретрай поверх mesh.
Credentials chain: откуда берутся права
Ни в одном из примеров выше нет ключей — и это не упрощение, а суть. SDK ищет креды по цепочке: переменные окружения → профиль из ~/.aws/ → web identity token (IRSA в EKS) → роль контейнера/инстанса. Эффект: один и тот же код на ноутбуке работает под вашим SSO-профилем, в EKS — под ролью ServiceAccount-а, в ECS — под task role. Конфигурируется не код, а окружение.
Антипаттерн, закрывающий это преимущество: accessKeyId/secretKey в application.yml. Ключи утекают через git и логи, не ротируются и превращают перенос сервиса в квест. Если в репозитории есть строка aws.secret — это инцидент, который ещё не случился. (Gitleaks из security-гайда ловит ровно это.)
Spring Cloud AWS: меньше обвязки
Поверх SDK существует Spring Cloud AWS (проект awspring) — автоконфигурация клиентов из application.yml и высокоуровневые удобства. Самое полезное — SQS-листенер в стиле Kafka:
@Component
public class OrderEventsSqsConsumer {
@SqsListener("order-events")
public void handle(OrderEventPayload payload) {
dispatcher.dispatch(new RegisterOrderEventUseCase(payload));
}
}
Висибилити-таймаут, удаление после успеха, поллинг — под капотом; ваша забота — идемпотентность обработчика (SQS — at-least-once, как и всё в этом мире). Вторая полезная интеграция — конфигурация и секреты из Parameter Store/Secrets Manager как property source: spring.config.import: aws-secretsmanager:/prod/order-service/ — и секреты не существуют нигде, кроме Secrets Manager (подробнее).
S3-паттерны из Spring — загрузка, presigned URLs, multipart — разобраны отдельной статьёй в object-storage разделе; здесь не дублируем.
Тестирование: LocalStack
LocalStack — локальная имитация AWS API; с Testcontainers интеграционный тест против «AWS» выглядит как обычный тест против PG:
@Testcontainers
class OrderEventsSqsConsumerTest {
@Container
static LocalStackContainer localstack =
new LocalStackContainer(DockerImageName.parse("localstack/localstack:3"))
.withServices(SQS);
@DynamicPropertySource
static void aws(DynamicPropertyRegistry registry) {
registry.add("spring.cloud.aws.endpoint", () -> localstack.getEndpoint().toString());
registry.add("spring.cloud.aws.region.static", localstack::getRegion);
}
@Test
void processesOrderEvent() {
sendToQueue("order-events", paidOrderPayload());
await().untilAsserted(() -> assertThat(repository.findEvents()).hasSize(1));
}
}
Границы честности: LocalStack отлично имитирует API-контракты (очереди, бакеты, события) и не имитирует IAM-политики и лимиты — права проверяются только на стейдже. Тестовая стратегия та же, что в общем гайде: бизнес-логика — без AWS вовсе (порт + мок), интеграция адаптера — LocalStack, права и сеть — стейдж.
Цена вызовов: о чём помнить в коде
Облако превращает некоторые строчки кода в строчки счёта. Минимальный список для ревью: поллинг SQS — только long polling (waitTimeSeconds: 20, короткий поллинг платит за пустые ответы); S3-запросы в цикле по одному объекту — батчами или префиксным листингом; межсервисный обмен — внутри AZ/региона, NAT-трафик к AWS-сервисам — через VPC endpoints; CloudWatch-метрики со взрывной кардинальностью — те же правила, но с прайс-листом. Не оптимизация, а гигиена: цена за запрос видна заранее, в отличие от инцидента.
Антипаттерны
| Антипаттерн | Чем кончается | Что взамен |
|---|---|---|
| Ключи в application.yml / git | Утечка, ротация руками, инцидент | Credentials chain: IRSA, task role, SSO |
| SDK v1 в новом коде | Два SDK в одном сервисе, старые баги | Только software.amazon.awssdk |
| Свой retry поверх retry SDK | Лавина повторов при деградации | Настроить retry SDK, свой — выключить |
| Юнит-тесты с реальным AWS | Медленно, дорого, флаки, нужен интернет | Порт+мок; LocalStack для адаптера |
| Короткий поллинг SQS | Оплата пустых ответов 24/7 | Long polling 20s |
| Клиент на каждый вызов | Сотни TLS-рукопожатий, утечки | Клиент-бин, один на сервис |
Что почитать дальше
- Fundamentals — IAM и credentials chain со стороны платформы.
- Spring + AWS SDK для S3 — файловые паттерны подробно.
- Безопасность и наблюдаемость — Secrets Manager и CloudWatch из кода.
- Test Strategy Style Guide — куда LocalStack встраивается в пирамиду.