jkisolo.com

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.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Rediscovering Joy: 9 Activities That Bring True Happiness

Explore nine fulfilling activities that can replace mindless phone scrolling and genuinely boost your happiness.

Effective Communication: Navigating Social Interactions

Explore practical tips for enhancing communication skills and building connections in social settings.

How to Rapidly Sabotage Your Social Life

Discover the quickest ways to dismantle your social connections through negative behaviors and attitudes.