Files
gh-giuseppe-trisciuoglio-de…/skills/aws-java/aws-sdk-java-v2-messaging/references/spring-boot-integration.md
2025-11-29 18:28:30 +08:00

7.7 KiB

Spring Boot Integration Reference

Configuration

Basic Bean Configuration

@Configuration
public class MessagingConfiguration {

    @Bean
    public SqsClient sqsClient() {
        return SqsClient.builder()
            .region(Region.US_EAST_1)
            .build();
    }

    @Bean
    public SnsClient snsClient() {
        return SnsClient.builder()
            .region(Region.US_EAST_1)
            .build();
    }
}

Configuration Properties

# application.yml
aws:
  sqs:
    queue-url: https://sqs.us-east-1.amazonaws.com/123456789012/my-queue
  sns:
    topic-arn: arn:aws:sns:us-east-1:123456789012:my-topic

Service Layer Integration

SQS Message Service

@Service
@RequiredArgsConstructor
public class SqsMessageService {

    private final SqsClient sqsClient;
    private final ObjectMapper objectMapper;

    @Value("${aws.sqs.queue-url}")
    private String queueUrl;

    public <T> void sendMessage(T message) {
        try {
            String jsonMessage = objectMapper.writeValueAsString(message);

            SendMessageRequest request = SendMessageRequest.builder()
                .queueUrl(queueUrl)
                .messageBody(jsonMessage)
                .build();

            sqsClient.sendMessage(request);

        } catch (Exception e) {
            throw new RuntimeException("Failed to send SQS message", e);
        }
    }

    public <T> List<T> receiveMessages(Class<T> messageType) {
        ReceiveMessageRequest request = ReceiveMessageRequest.builder()
            .queueUrl(queueUrl)
            .maxNumberOfMessages(10)
            .waitTimeSeconds(20)
            .build();

        ReceiveMessageResponse response = sqsClient.receiveMessage(request);

        return response.messages().stream()
            .map(msg -> {
                try {
                    return objectMapper.readValue(msg.body(), messageType);
                } catch (Exception e) {
                    throw new RuntimeException("Failed to parse message", e);
                }
            })
            .collect(Collectors.toList());
    }

    public void deleteMessage(String receiptHandle) {
        DeleteMessageRequest request = DeleteMessageRequest.builder()
            .queueUrl(queueUrl)
            .receiptHandle(receiptHandle)
            .build();

        sqsClient.deleteMessage(request);
    }
}

SNS Notification Service

@Service
@RequiredArgsConstructor
public class SnsNotificationService {

    private final SnsClient snsClient;
    private final ObjectMapper objectMapper;

    @Value("${aws.sns.topic-arn}")
    private String topicArn;

    public void publishNotification(String subject, Object message) {
        try {
            String jsonMessage = objectMapper.writeValueAsString(message);

            PublishRequest request = PublishRequest.builder()
                .topicArn(topicArn)
                .subject(subject)
                .message(jsonMessage)
                .build();

            snsClient.publish(request);

        } catch (Exception e) {
            throw new RuntimeException("Failed to publish SNS notification", e);
        }
    }
}

Message Listener Pattern

Scheduled Polling

@Service
@RequiredArgsConstructor
public class SqsMessageListener {

    private final SqsClient sqsClient;
    private final ObjectMapper objectMapper;

    @Value("${aws.sqs.queue-url}")
    private String queueUrl;

    @Scheduled(fixedDelay = 5000)
    public void pollMessages() {
        ReceiveMessageRequest request = ReceiveMessageRequest.builder()
            .queueUrl(queueUrl)
            .maxNumberOfMessages(10)
            .waitTimeSeconds(20)
            .build();

        ReceiveMessageResponse response = sqsClient.receiveMessage(request);

        response.messages().forEach(this::processMessage);
    }

    private void processMessage(Message message) {
        try {
            // Process message
            System.out.println("Processing: " + message.body());

            // Delete message after successful processing
            deleteMessage(message.receiptHandle());

        } catch (Exception e) {
            // Handle error - message will become visible again
            System.err.println("Failed to process message: " + e.getMessage());
        }
    }

    private void deleteMessage(String receiptHandle) {
        DeleteMessageRequest request = DeleteMessageRequest.builder()
            .queueUrl(queueUrl)
            .receiptHandle(receiptHandle)
            .build();

        sqsClient.deleteMessage(request);
    }
}

Pub/Sub Pattern Integration

Configuration for Pub/Sub

@Configuration
@RequiredArgsConstructor
public class PubSubConfiguration {

    private final SnsClient snsClient;
    private final SqsClient sqsClient;

    @Bean
    @DependsOn("sqsClient")
    public String setupPubSub() {
        // Create SNS topic
        String topicArn = snsClient.createTopic(CreateTopicRequest.builder()
            .name("order-events")
            .build()).topicArn();

        // Create SQS queue
        String queueUrl = sqsClient.createQueue(CreateQueueRequest.builder()
            .queueName("order-processor")
            .build()).queueUrl();

        // Get queue ARN
        String queueArn = sqsClient.getQueueAttributes(GetQueueAttributesRequest.builder()
            .queueUrl(queueUrl)
            .attributeNames(QueueAttributeName.QUEUE_ARN)
            .build()).attributes().get(QueueAttributeName.QUEUE_ARN);

        // Subscribe SQS to SNS
        snsClient.subscribe(SubscribeRequest.builder()
            .protocol("sqs")
            .endpoint(queueArn)
            .topicArn(topicArn)
            .build());

        return topicArn;
    }
}

Error Handling Patterns

Retry Mechanism

@Service
@RequiredArgsConstructor
public class RetryableSqsService {

    private final SqsClient sqsClient;
    private final RetryTemplate retryTemplate;

    public void sendMessageWithRetry(String queueUrl, String messageBody) {
        retryTemplate.execute(context -> {
            try {
                SendMessageRequest request = SendMessageRequest.builder()
                    .queueUrl(queueUrl)
                    .messageBody(messageBody)
                    .build();

                sqsClient.sendMessage(request);
                return null;
            } catch (Exception e) {
                throw new RetryableException("Failed to send message", e);
            }
        });
    }
}

Testing Integration

LocalStack Configuration

@TestConfiguration
public class LocalStackMessagingConfig {

    @Container
    static LocalStackContainer localstack = new LocalStackContainer(
        DockerImageName.parse("localstack/localstack:3.0"))
        .withServices(
            LocalStackContainer.Service.SQS,
            LocalStackContainer.Service.SNS
        );

    @Bean
    public SqsClient sqsClient() {
        return SqsClient.builder()
            .region(Region.US_EAST_1)
            .endpointOverride(
                localstack.getEndpointOverride(LocalStackContainer.Service.SQS))
            .credentialsProvider(StaticCredentialsProvider.create(
                AwsBasicCredentials.create(
                    localstack.getAccessKey(),
                    localstack.getSecretKey())))
            .build();
    }

    @Bean
    public SnsClient snsClient() {
        return SnsClient.builder()
            .region(Region.US_EAST_1)
            .endpointOverride(
                localstack.getEndpointOverride(LocalStackContainer.Service.SNS))
            .credentialsProvider(StaticCredentialsProvider.create(
                AwsBasicCredentials.create(
                    localstack.getAccessKey(),
                    localstack.getSecretKey())))
            .build();
    }
}