Implementing a Spring Boot Alert System with AWS SNS and SQS
Written on
Chapter 1: Introduction to Alert Streaming
In this guide, we will walk through the creation of a Spring Boot application named Alert Producer. This application will generate alert events, and we will also implement two consumer applications, Alert Consumer A and Alert Consumer B. These consumers will handle the events emitted by the Alert Producer. The process involves sending alerts to AWS SNS, which then distributes them to two separate AWS SQS queues, each designated for one of the consumers. The Alert Consumers will subsequently retrieve and process these alert messages from their respective queues.
To facilitate development and testing, we will leverage LocalStack to emulate key AWS services on our local machine, along with Spring Cloud AWS to streamline interactions with these AWS-managed services.
Let’s dive in!
Prerequisites
Before you begin, ensure that you have Java 17 or higher and Docker installed on your system.
Creating the Alerts Application Directory
First, create a directory called alerts-app in your workspace. This is where we will develop both the Alert Producer and Consumer applications.
Creating the Alert Producer
We will initiate a Spring Boot application using Spring Initializr. The application will be named alert-producer, and we will include dependencies for Spring Web and Validation. We will use Spring Boot version 3.2.4 and Java 17. You can find the setup details linked below.
After clicking the GENERATE button, download and extract the zip file into the alerts-app directory. Open the alert-producer project in your IDE.
Adding the Spring Cloud AWS Library
Next, update the pom.xml file to include the Spring Cloud AWS library. Here’s how you should modify the file:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sns</artifactId>
<version>3.1.1</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${spring-cloud-aws.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Organizing the Code Structure
For better organization, create the following packages within the com.example.alertproducer root package: controller, event, and producer.
Defining the Alert Event Record
Within the event package, define the AlertEvent record as follows:
package com.example.alertproducer.event;
public record AlertEvent(int level, String message) {}
The AlertEvent record will represent the alerts that we publish.
Creating the Alert Producer Class
In the producer package, implement the AlertProducer class:
package com.example.alertproducer.producer;
import com.example.alertproducer.event.AlertEvent;
import io.awspring.cloud.sns.core.SnsTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AlertProducer {
private final SnsTemplate snsTemplate;
@Value("${aws.sns.destination}")
private String destination;
public AlertProducer(SnsTemplate snsTemplate) {
this.snsTemplate = snsTemplate;}
public void send(AlertEvent event) {
snsTemplate.convertAndSend(destination, event);}
}
In the AlertProducer class, we use SnsTemplate to send alert events.
Defining the DTO Record
In the controller package, create the CreateAlertRequest DTO (Data Transfer Object):
package com.example.alertproducer.controller;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public record CreateAlertRequest(@Min(1) @Max(10) int level, @NotBlank String message) {}
This DTO will be used in the POST method of the AlertController.
Creating the Controller Class
Now, implement the AlertController class in the controller package:
package com.example.alertproducer.controller;
import com.example.alertproducer.event.AlertEvent;
import com.example.alertproducer.producer.AlertProducer;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/alerts")
public class AlertController {
private static final Logger log = LoggerFactory.getLogger(AlertController.class);
private final AlertProducer alertProducer;
public AlertController(AlertProducer alertProducer) {
this.alertProducer = alertProducer;}
@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public AlertEvent sendAlert(@Valid @RequestBody CreateAlertRequest request) {
AlertEvent alertEvent = new AlertEvent(request.level(), request.message());
alertProducer.send(alertEvent);
log.info("Alert sent! {}", alertEvent);
return alertEvent;
}
}
The AlertController class will manage HTTP requests related to alerts, utilizing the AlertProducer to send events.
Updating Application Properties
Next, modify the application.properties file to include the following configuration:
spring.application.name=alert-producer
spring.cloud.aws.credentials.access-key=key
spring.cloud.aws.credentials.secret-key=secret
spring.cloud.aws.region.static=eu-west-1
spring.cloud.aws.sns.endpoint=http://localhost.localstack.cloud:4566
aws.sns.destination=alert-topic
Overview of Properties:
- spring.cloud.aws.credentials.access-key: Sets the AWS access key for authentication.
- spring.cloud.aws.credentials.secret-key: Sets the AWS secret key for authentication.
- spring.cloud.aws.region.static: Specifies the AWS region.
- spring.cloud.aws.sns.endpoint: Points to the LocalStack instance running locally.
- aws.sns.destination: Defines the SNS topic name for sending alerts.
Creating Alert Consumer A
Use Spring Initializr again to create another Spring Boot application named alert-consumer-a, including only the Spring Web dependency.
Add the Spring Cloud AWS library similarly as before and create the following packages within com.example.alertconsumera: event and listener.
Defining the Event Record
In the event package, define the AlertEvent record:
package com.example.alertconsumera.event;
public record AlertEvent(int level, String message) {}
Creating the Listener Class
In the listener package, create the AlertListener class:
package com.example.alertconsumera.listener;
import com.example.alertconsumera.event.AlertEvent;
import io.awspring.cloud.sqs.annotation.SqsListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class AlertListener {
private static final Logger log = LoggerFactory.getLogger(AlertListener.class);
@SqsListener("${aws.sqs.destination}")
public void handleAlertEvent(AlertEvent alertEvent) {
log.info("Received alertEvent: {}", alertEvent);}
}
The AlertListener class uses the @SqsListener annotation to process messages from the specified SQS queue.
Updating Application Properties for Consumer A
Update the application.properties file for alert-consumer-a:
spring.application.name=alert-consumer-a
server.port=9080
spring.cloud.aws.credentials.access-key=key
spring.cloud.aws.credentials.secret-key=secret
spring.cloud.aws.region.static=eu-west-1
spring.cloud.aws.sqs.endpoint=http://localhost.localstack.cloud:4566
aws.sqs.destination=alert-consumer-a-queue
Creating Alert Consumer B
Repeat the process to create a third Spring Boot application named alert-consumer-b, with similar configurations as alert-consumer-a.
Updating Application Properties for Consumer B
Update the application.properties for alert-consumer-b:
spring.application.name=alert-consumer-b
server.port=9081
spring.cloud.aws.credentials.access-key=key
spring.cloud.aws.credentials.secret-key=secret
spring.cloud.aws.region.static=eu-west-1
spring.cloud.aws.sqs.endpoint=http://localhost.localstack.cloud:4566
aws.sqs.destination=alert-consumer-b-queue
Creating Docker Compose File
In the root of alerts-app, create a docker-compose.yml file:
version: "3.8"
services:
localstack:
container_name: localstack
image: localstack/localstack:3.2.0
ports:
- "127.0.0.1:4510-4559:4510-4559"
- "127.0.0.1:4566:4566"
environment:
- LOCALSTACK_HOSTNAME=localhost.localstack.cloud
- AWS_ACCESS_KEY_ID=key
- AWS_SECRET_ACCESS_KEY=secret
- AWS_DEFAULT_REGION=eu-west-1
- SERVICES=sns,sqs
volumes:
- "$PWD/tmp/localstack:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
default:
aliases:
- localhost.localstack.cloud
Creating LocalStack Initialization Script
In the root folder, create the init-localstack.sh script:
#!/usr/bin/env bash
if ! [[ $(docker ps -q -f name=localstack) ]]; then
echo "WARNING: The localstack Docker container is not running. Please, start it first."
exit 1
fi
echo
echo "Initializing LocalStack"
echo "======================="
echo "Creating alert-topic in SNS"
docker exec -t localstack aws --endpoint-url=http://localhost:4566 sns create-topic --name alert-topic
echo "Creating alert-consumer-a-queue in SQS"
docker exec -t localstack aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name alert-consumer-a-queue
echo "Creating alert-consumer-b-queue in SQS"
docker exec -t localstack aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name alert-consumer-b-queue
echo "Subscribing alert-consumer-a-queue to alert-topic"
docker exec -t localstack aws --endpoint-url=http://localhost:4566 sns subscribe
--topic-arn arn:aws:sns:eu-west-1:000000000000:alert-topic
—protocol sqs
--attributes '{"RawMessageDelivery":"true"}'
—notification-endpoint arn:aws:sqs:eu-west-1:000000000000:alert-consumer-a-queue
echo "Subscribing alert-consumer-b-queue to alert-topic"
docker exec -t localstack aws --endpoint-url=http://localhost:4566 sns subscribe
--topic-arn arn:aws:sns:eu-west-1:000000000000:alert-topic
—protocol sqs
--attributes '{"RawMessageDelivery":"true"}'
—notification-endpoint arn:aws:sqs:eu-west-1:000000000000:alert-consumer-b-queue
echo "LocalStack initialized successfully"
After creating the script, make it executable with:
chmod +x init-localstack.sh
Starting the Environment
In a terminal, navigate to the alerts-app directory and run:
DEBUG=1 docker compose up -d
Next, execute the initialization script:
./init-localstack.sh
If everything is set up correctly, you will see confirmation messages about the successful creation of topics and queues.
Starting the Alert Applications
To start the applications, run the following commands in separate terminal windows:
For the Alert Producer:
cd alerts-app/alert-producer
./mvnw clean spring-boot:run
For Alert Consumer A:
cd alerts-app/alert-consumer-a
./mvnw clean spring-boot:run
For Alert Consumer B:
cd alerts-app/alert-consumer-b
./mvnw clean spring-boot:run
Testing Alert Emission
To test the setup, submit alerts using the Alert Producer's endpoint with the following CURL commands:
curl -X POST localhost:8080/api/alerts
-H 'Content-Type: application/json'
-d '{"level":7, "message":"Heavy rain & strong winds expected. Stay indoors."}'
curl -X POST localhost:8080/api/alerts
-H 'Content-Type: application/json'
-d '{"level":8, "message":"Flood warning! Move to higher ground."}'
curl -X POST localhost:8080/api/alerts
-H 'Content-Type: application/json'
-d '{"level":8, "message":"Hurricane alert! Follow local evacuation orders."}'
curl -X POST localhost:8080/api/alerts
-H 'Content-Type: application/json'
-d '{"level":5, "message":"High pollution alert! Limit outdoor activities."}'
curl -X POST localhost:8080/api/alerts
-H 'Content-Type: application/json'
-d '{"level":7, "message":"Thunderstorm warning! Seek indoor shelter."}'
You should observe the logs of the Alert Consumer applications to confirm that they are receiving the alerts properly.
Spring Boot with AWS SNS | Send Notifications from SNS Topic to SQS Queue and Email
Spring Boot with Amazon SQS | Spring Cloud AWS | Amazon Web Services
Shutdown
To stop the applications, press Ctrl+C in the terminal windows where the Alert Producer, Alert Consumer A, and Alert Consumer B are running. To stop the LocalStack Docker container, execute:
docker compose down -v
Conclusion
In this article, we have successfully developed three Spring Boot applications: Alert Producer, Alert Consumer A, and Alert Consumer B. The Alert Producer sends events to AWS SNS, which then distributes them to two AWS SQS queues for processing by the consumers. All services were run locally using LocalStack. Additionally, we utilized the Spring Cloud AWS library to simplify our interactions with AWS services. Finally, we tested the setup to ensure everything operates as intended.
Additional Readings
Support and Engagement
If you found this article helpful, consider supporting it by engaging with it: clap, highlight, or reply. I’m happy to answer any questions you may have. You can also share it on social media or follow me on Medium, LinkedIn, Twitter, and GitHub. Don’t forget to subscribe to my newsletter for the latest updates.