Compartir comentarios
Las respuestas se generan en base a la documentación.

Crear el proyecto Spring Boot

Configurar el proyecto

Crea un proyecto Spring Boot desde Spring Initializr seleccionando el iniciador (starter) de Testcontainers. Los iniciadores de Spring Cloud AWS no están disponibles en Spring Initializr, por lo que debes añadirlos manualmente.

Alternativamente, puedes clonar el repositorio de la guía.

Añade el BOM de Spring Cloud AWS a la gestión de dependencias de tu proyecto y añade los iniciadores de S3 y SQS como dependencias. Testcontainers proporciona un módulo LocalStack para probar las integraciones con servicios de AWS. También necesitarás Awaitility para probar el procesamiento asíncrono de SQS.

Las dependencias clave en pom.xml son:

<properties>
    <java.version>17</java.version>
    <testcontainers.version>2.0.4</testcontainers.version>
    <awspring.version>3.0.3</awspring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-aws-starter-s3</artifactId>
    </dependency>
    <dependency>
        <groupId>io.awspring.cloud</groupId>
        <artifactId>spring-cloud-aws-starter-sqs</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-testcontainers</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers-junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers-localstack</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.awaitility</groupId>
        <artifactId>awaitility</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.awspring.cloud</groupId>
            <artifactId>spring-cloud-aws-dependencies</artifactId>
            <version>${awspring.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Crear las propiedades de configuración

Para hacer que los nombres de la cola SQS y del bucket S3 sean configurables, crea un record ApplicationProperties:

package com.testcontainers.demo;

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

@ConfigurationProperties(prefix = "app")
public record ApplicationProperties(String queue, String bucket) {}

Luego, añade @ConfigurationPropertiesScan a la clase de aplicación principal para que Spring busque automáticamente clases anotadas con @ConfigurationProperties y las registre como beans:

package com.testcontainers.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Implementar StorageService para S3

Spring Cloud AWS proporciona abstracciones de más alto nivel como S3Template con métodos convenientes para subir y descargar archivos. Crea una clase StorageService:

package com.testcontainers.demo;

import io.awspring.cloud.s3.S3Template;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.stereotype.Service;

@Service
public class StorageService {

  private final S3Template s3Template;

  public StorageService(S3Template s3Template) {
    this.s3Template = s3Template;
  }

  public void upload(String bucketName, String key, InputStream stream) {
    this.s3Template.upload(bucketName, key, stream);
  }

  public InputStream download(String bucketName, String key)
    throws IOException {
    return this.s3Template.download(bucketName, key).getInputStream();
  }

  public String downloadAsString(String bucketName, String key)
    throws IOException {
    try (InputStream is = this.download(bucketName, key)) {
      return new String(is.readAllBytes());
    }
  }
}

Crear el modelo de mensaje SQS

Crea un record Message que represente la carga útil (payload) que envías a la cola SQS:

package com.testcontainers.demo;

import java.util.UUID;

public record Message(UUID uuid, String content) {}

Implementar el emisor de mensajes

Crea MessageSender, el cual utiliza SqsTemplate para publicar mensajes:

package com.testcontainers.demo;

import io.awspring.cloud.sqs.operations.SqsTemplate;
import org.springframework.stereotype.Service;

@Service
public class MessageSender {

  private final SqsTemplate sqsTemplate;

  public MessageSender(SqsTemplate sqsTemplate) {
    this.sqsTemplate = sqsTemplate;
  }

  public void publish(String queueName, Message message) {
    sqsTemplate.send(to -> to.queue(queueName).payload(message));
  }
}

Implementar el receptor de mensajes (listener)

Crea MessageListener con un método manejador anotado con @SqsListener. Cuando llega un mensaje, el listener sube el contenido a un bucket de S3 utilizando el UUID del mensaje como clave:

package com.testcontainers.demo;

import io.awspring.cloud.sqs.annotation.SqsListener;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.stereotype.Service;

@Service
public class MessageListener {

  private final StorageService storageService;
  private final ApplicationProperties properties;

  public MessageListener(
    StorageService storageService,
    ApplicationProperties properties
  ) {
    this.storageService = storageService;
    this.properties = properties;
  }

  @SqsListener(queueNames = { "${app.queue}" })
  public void handle(Message message) {
    String bucketName = this.properties.bucket();
    String key = message.uuid().toString();
    ByteArrayInputStream is = new ByteArrayInputStream(
      message.content().getBytes(StandardCharsets.UTF_8)
    );
    this.storageService.upload(bucketName, key, is);
  }
}

La expresión ${app.queue} lee el nombre de la cola desde la configuración de la aplicación en lugar de tenerlo codificado de forma fija (hard-coded).