# Crear el proyecto Spring Boot


## Configurar el proyecto

Crea un proyecto Spring Boot desde [Spring Initializr](https://start.spring.io) 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](https://github.com/testcontainers/tc-guide-testing-aws-service-integrations-using-localstack).

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](https://testcontainers.com/modules/localstack/) para probar las integraciones con servicios de AWS. También necesitarás [Awaitility](http://www.awaitility.org/) para probar el procesamiento asíncrono de SQS.

Las dependencias clave en `pom.xml` son:

```xml
<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`:

```java
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:

```java
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`:

```java
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:

```java
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:

```java
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:

```java
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).

