DEV Community

Cover image for Create an AWS SNS Topic Using CDK and consume messages with a Spring Boot Microservice
Kevin Lactio Kemta
Kevin Lactio Kemta

Posted on

Create an AWS SNS Topic Using CDK and consume messages with a Spring Boot Microservice

Day 018 - 100DaysAWSIaCDevopsChallenge

Amazon SNS (Simple Notification Service) is a fully managed Amazon messaging service that allows large number of subscribers using different delivery protocoles, such as HTTP/HTTPS, email, SQS, SMS and AWS Lambda.
It designed for scalable, high-throughput, push-bash messaging. It enables applications, microservices, and systems to communicate with each other asynchroniously in real time.
The core concept of SNS revolvers around topicsand subscriptions.

Subscribe and Consume a SNS Messages using Springboot application

This guide demonstrates how to create a Spring Boot application that subscribes to and processes messages from an SNS topic. The infrastructure, built using AWS CDK (in Java), includes the following components:

  • A VPC with a public Subnet to host an EC2 instance where the Spring Boot application will run.
  • An Internet Gateway to provide the EC2 instance with internet access for downloading dependencies.
  • An SNS Topic for publishing messages.
  • An EC2 Instance for hosting the Spring Boot application.
  • An IAM Role to grant the EC2 instance permissions to receive messages from the SNS topic (critical for secure communication).

Create the Infrastructure

Set up the necessary infrastructure using CDK (Java)

diagram of infrastructure

VPC & Subnet + Internet Gateway
// constructs/NetworkConstruct.java
public class NetworkContruct extends Construct {
  private final IVpc vpc;
  public NetworkContruct(Construct scope, String id, StackProps props) {
    super(scope, id);
    this.vpc =
        new Vpc(
            this,
            "VpcResource",
            VpcProps.builder()
                .vpcName("my-vpc")
                .enableDnsHostnames(true)
                .enableDnsSupport(true)
                .createInternetGateway(true)
                .ipProtocol(IpProtocol.IPV4_ONLY)
                .ipAddresses(IpAddresses.cidr("10.0.0.1/16"))
                .maxAzs(1)
                .subnetConfiguration(
                    List.of(
                        SubnetConfiguration.builder()
                            .name("Public-Subnet")
                            .mapPublicIpOnLaunch(true)
                            .subnetType(SubnetType.PUBLIC)
                            .build()))
                .build());
  }
  public IVpc getVpc() {
    return vpc;
  }
}

// MyStatck.java
public class MyStack extends Stack {
  public MyStack(final Construct scope, final String id, final StackProps props) {
    super(scope, id, props);
    IVpc vpc = new NetworkContruct(this, "NetworkResource", props).getVpc();
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code will create:

  • A VPC named my-vpc and enable DNS hostname enabled.
  • A public subnet named Public-Subnet which allows resources to attach a public IP (if configured with one).
  • An Internet Gateway to enable internet traffic.
SNS Topic
// MyStatck.java
public class MyStack extends Stack {
  public MyStack(final Construct scope, final String id, final StackProps props) {
    super(scope, id, props);
    //...
    String topicName = "example-topic-main";
    ITopic topic = new Topic(
        this, "TopicResource", TopicProps.builder()
        .topicName(topicName)
        .fifo(false)
        .build());
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code creates an SNS Topic named example-topic-main. This topic will be used for publishing messages, which the Spring Boot application will subscribe to and process.

EC2 Instance for Hosting the Spring Boot Application
// MyStatck.java
public class MyStack extends Stack {
  public MyStack(final Construct scope, final String id, final StackProps props) {
    super(scope, id, props);
    //...
    IVpc vpc; // previously instanciated
    String topicName;
    int port = 8089;
    ITopic topic; // previously instanciated

    ComputerConstruct webserver =
            new ComputerConstruct(
                this, "ComputerResource", ComputerProps.builder().vpc(vpc).port(port).build(), props);
    IInstance instance = webserver.getComputer();
    webserver.addPolicyToComputer(
        PolicyStatement.Builder.create()
            .effect(Effect.ALLOW)
            .resources(List.of(topic.getTopicArn()))
            .actions(
                List.of(
                    "sns:ConfirmSubscription",
                    "sns:Subscribe",
                    "sns:GetTopicAttributes",
                    "sns:ListTopics"))
            .build());
    ITopicSubscription endpointSubscription =
        new UrlSubscription(
            "http://%s:%d/topics/%s"
                .formatted(instance.getInstancePublicDnsName(), port, topicName),
            UrlSubscriptionProps.builder()
                .rawMessageDelivery(false)
                .protocol(SubscriptionProtocol.HTTP)
                .build());
    topic.addSubscription(endpointSubscription);
  }
}
Enter fullscreen mode Exit fullscreen mode

ComputerConstruct.java [↗]
The above CDK construct will create the following resources:

  • A Security Group named Webserver-security-group that allows inbound traffic on Port 22 for SSH connections and allows inbound traffic on Port 8089, which is the application connection port.
  • A Key Pair named ws-keypair that will be used to connect to the app host via SSH. Since we are using CDK to build the infrastructure, if you need to download the private key (PEM file) after deployment, refer to my previous article on How the retrieve the private key file PEM after Cloudformation or CDK stack creation[↗].
  • An Ec2 Instance named Webserver-Instance.
  • An IAM Role for the Ec2 Instance named webserver-role, which allows the spring Boot application hosted on the Ec2 Instance to establish connections with the Amazon SQS Queue (already created) and perform actions: sns:ConfirmSubscription, sns:Subscribe, sns:GetTopicAttributes and sns:ListTopics.
  • An SNS Subscription for the HTTP endpoint, allowing the Spring Boot application hosted on the EC2 instance to receive and process messages from the SNS topic.

Create the stack

// Day17App.java
public class Day017App {
  public static void main(final String[] args) {
    App app = new App();
    new MyStack(app,"Day017Stack",
        StackProps.builder()
                .env(
                    Environment.builder()
                        .account(System.getenv("CDK_DEFAULT_ACCOUNT"))
                        .region(System.getenv("CDK_DEFAULT_REGION"))
                        .build())
                .build());
    app.synth();
  }
}
Enter fullscreen mode Exit fullscreen mode

Create SpringBoot Subscriber Application

To keep things simple and avoid complicating my life, I will use Spring Cloud AWS Docs[↗]

Spring Cloud AWS simplifies using AWS managed services in a Spring Framework and Spring Boot applications. It offers a convenient way to interact with AWS provided services using well-known Spring idioms and APIs.

To configure the SNS service, add the following beans in the configuration class:

@Configuration
public class ApplicationConfiguration {
    @Bean
    public AwsRegionProvider customRegionProvider() {
        return new InstanceProfileRegionProvider();
    }
    @Bean
    public AwsCredentialsProvider customInstanceCredProvider() {
        return  InstanceProfileCredentialsProvider.builder()
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, add a controller designed to handle incoming notifications from an AWS SNS (Simple Notification Service) topic. This controller allows the application to subscribe to the SNS topic and receive messages sent to it. It utilizes annotations from the Spring Cloud AWS library to manage SNS message mappings effectively.

@RestController
@RequestMapping("/topics/${sns.topic.name}") // ${sns.topic.name} will return example-topic-main
@Slf4j
public class ConsumeNotificationResource {

  @NotificationSubscriptionMapping
  public void confirmSubscription(NotificationStatus status) {
    status.confirmSubscription();
    log.info("Subscription confirmed");
  }

  @NotificationMessageMapping
  public void receiveMessage(@NotificationSubject String subject, @NotificationMessage String message) {
    log.info(
        """
        ************************* SNS Notification ***************
        * Subject : {}
        * Content: {}
        * Date : {}
        **********************************************************
        """,
        subject,
        message,
        LocalDateTime.now());
  }

  @NotificationUnsubscribeConfirmationMapping
  public void unsubscribe(NotificationStatus status) {
    status.confirmSubscription();
    log.info("Unsubscription confirmed");
  }
}
Enter fullscreen mode Exit fullscreen mode

You can find the full project in my GitHub repo[↗]

Deployment

⚠️⚠️ Before run the deployment command ensure that you have java installed on your host machine. I used Java 21 under MacOs to build this insfrastructure.

Open the terminal anywhere and run the following commande:

git clone https://github.com/nivekalara237/100DaysTerraformAWSDevops.git
cd 100DaysTerraformAWSDevops/day_018
cdk bootstrap --profile cdk-user
cdk deploy --profile cdk-user Day018Stack
Enter fullscreen mode Exit fullscreen mode

Resut

Image description


Your can find the full source code here #day_18

GitHub logo nivekalara237 / 100DaysTerraformAWSDevops

Thriving with AWS and Terraform

Top comments (0)