Skip to main content

Spring Boot SNS Lambda DynamoDB

Description

In this example we will publish messages to DynamoDB via SNS and Lambda from a Spring Boot application.

spring-boot-sns-lambda-dynamodb-01.png

The example is based on Spring Boot SNS Lambda and Lambda Person Request DynamoDB.

Prerequisites

Run LocalStack

version: '3.8'

services:
localstack:
image: localstack/localstack
container_name: localstack_main
ports:
- "4566-4599:4566-4599"
environment:
- SERVICES=sns,s3,sqs,dynamodb,ses,lambda,logs,stepfunctions
- DYNAMODB_SHARE_DB=1
- DEBUG=1
- DATA_DIR=/tmp/localstack/data
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"

Create Topic

awslocal sns create-topic --name localstack-topic --region us-east-1

Create a Subscription

awslocal sns subscribe \
--region us-east-1 \
--protocol lambda \
--topic-arn arn:aws:sns:us-east-1:000000000000:localstack-topic \
--notification-endpoint arn:aws:lambda:us-east-1:000000000000:function:localstack-lambda-sns

Create DynamoDB Table

awslocal dynamodb create-table \
--table-name person \
--key-schema AttributeName=id,KeyType=HASH \
--attribute-definitions AttributeName=id,AttributeType=N \
--billing-mode PAY_PER_REQUEST

Project Structure

.
├── sns-lambda-dynamodb
│   ├── localstack.cmd
│   ├── pom.xml
│   ├── README.md
│   └── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │   └── example
│   │   │   ├── Person.java
│   │   │   └── SnsRequestHandler.java
│   │   └── resources
│   └── test
│   └── java
└── spring-boot-sns-lambda
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src
├── main
│   ├── java
│   │   └── com
│   │   └── jreact
│   │   └── sns
│   │   ├── PersonRequest.java
│   │   ├── SNSAppConfig.java
│   │   └── SNSApp.java
│   └── resources
│   └── application.properties
└── test
└── java
  • Lambda

    • Source Code: sns-lambda-dynamodb

    • SnsRequestHandler.java

      package com.example;

      import com.amazonaws.services.lambda.runtime.Context;
      import com.amazonaws.services.lambda.runtime.LambdaLogger;
      import com.amazonaws.services.lambda.runtime.RequestHandler;
      import com.amazonaws.services.lambda.runtime.events.SNSEvent;

      import com.fasterxml.jackson.databind.ObjectMapper;
      import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
      import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
      import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
      import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
      import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
      import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
      import software.amazon.awssdk.regions.Region;
      import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

      import java.net.URI;
      import java.text.ParseException;
      import java.util.List;
      import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord;
      import lombok.extern.java.Log;
      import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;

      import java.util.Iterator;

      @Log
      public class SnsRequestHandler implements RequestHandler<SNSEvent, Boolean> {

      LambdaLogger logger;
      private SNSEvent event;

      //----- DynamoDB

      private static final String ACCESS_KEY = "test";
      private static final String SECRET_KEY = "test";
      private static String TABLE_NAME = "person";
      private static AwsCredentialsProvider credentials = StaticCredentialsProvider.create(
      AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY));

      private static Region region = Region.US_EAST_1;

      private static DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
      .region(region)
      .credentialsProvider(credentials)
      .endpointOverride(URI.create("https://localhost.localstack.cloud:4566"))
      .build();

      // -----

      @Override
      public Boolean handleRequest(SNSEvent event, Context context) {
      log.info(">>> start handleRequest...");
      logger = context.getLogger();
      List<SNSRecord> records = event.getRecords();

      if (!records.isEmpty()) {
      Iterator<SNSRecord> recordsIter = records.iterator();
      while (recordsIter.hasNext()) {
      processRecord(recordsIter.next());
      }
      }
      return Boolean.TRUE;
      }

      public void processRecord(SNSRecord record) {
      try {
      String message = record.getSNS().getMessage();
      logger.log(">>> message: " + message);

      //--- message to DynamoDB
      ObjectMapper objectMapper = new ObjectMapper();
      Person person = objectMapper.readValue(message, Person.class);
      log.info("JSON to POJO >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> " + person.getFirstName());
      String personId = "000000001";
      person.setId(Integer.parseInt(personId));
      addEntryToDynamoDB(person);
      //---

      } catch (Exception e) {
      throw new RuntimeException(e);
      }
      }

      private static void addEntryToDynamoDB(Person person) throws ParseException {
      try {
      DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
      .dynamoDbClient(dynamoDbClient)
      .build();

      // use the enhanced client to interact with the table
      DynamoDbTable<Person> table = enhancedClient.table(TABLE_NAME,
      TableSchema.fromBean(Person.class));
      table.putItem(person);

      log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Add Entry successfully");
      } catch (DynamoDbException exception) {
      log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Add Entry an error occurred: " + exception.getMessage());
      }
      }
      }
    • Create Lambda Function

      awslocal lambda create-function \
      --region us-east-1 \
      --function-name localstack-lambda-sns \
      --runtime java21 \
      --zip-file fileb://sns-trigger-lambda-0.0.1.jar \
      --handler com.example.SnsRequestHandler \
      --role arn:aws:iam::000000000000:role/example-lambda-noop-role \
      --timeout 120
  • Spring-Boot to SNS Lambda

    • Source Code: spring-boot-sns-lambda

    • Running

      mvn clean package
      mvn spring-boot:run
      //...
      2024-10-10T19:12:44.688+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : Started SNSApp in 0.553 seconds (process running for 0.674)
      2024-10-10T19:12:44.692+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : snsTopicARN: arn:aws:sns:us-east-1:000000000000:localstack-topic
      2024-10-10T19:12:44.692+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : Publishing SNS message: {"id":0,"firstName":"John","lastName":"Doe","age":26,"address":"Address01"}
      2024-10-10T19:12:44.793+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : SNS Message ID: 1ffa52b9-3f36-4b84-a296-137e6ad4f244
      2024-10-10T19:12:44.794+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : snsTopicARN: arn:aws:sns:us-east-1:000000000000:localstack-topic
      2024-10-10T19:12:44.794+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : Publishing SNS message: {"id":0,"firstName":"Jack","lastName":"Smith","age":30,"address":"Address02"}
      2024-10-10T19:12:44.799+02:00 INFO 236375 --- [ main] com.jreact.sns.SNSApp : SNS Message ID: 951c11fb-af8b-4191-91ce-2927491ceece
      //...

Output

  • Docker Log


    localstack_main | 2024-10-10T14:02:12.500 DEBUG --- [ns:$LATEST_0] l.s.l.i.version_manager : [localstack-lambda-sns-6a0ec8a6-756a-4de7-8b66-b11b5bd4a7c4] INFO: >>> start handleRequest...
    localstack_main | 2024-10-10T14:02:12.500 DEBUG --- [ns:$LATEST_0] l.s.l.i.version_manager : [localstack-lambda-sns-6a0ec8a6-756a-4de7-8b66-b11b5bd4a7c4] >>> message: {"id":0,"firstName":"John","lastName":"Doe","age":26,"address":"Address01"}Oct 10, 2024 2:02:12 PM com.example.SnsRequestHandler processRecord
    localstack_main | 2024-10-10T14:02:12.500 DEBUG --- [ns:$LATEST_0] l.s.l.i.version_manager : [localstack-lambda-sns-6a0ec8a6-756a-4de7-8b66-b11b5bd4a7c4] INFO: JSON to POJO >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> John
    localstack_main | 2024-10-10T14:02:12.500 DEBUG --- [ns:$LATEST_0] l.s.l.i.version_manager : [localstack-lambda-sns-6a0ec8a6-756a-4de7-8b66-b11b5bd4a7c4] Oct 10, 2024 2:02:12 PM com.example.SnsRequestHandler addEntryToDynamoDB
    localstack_main | 2024-10-10T14:02:12.500 DEBUG --- [ns:$LATEST_0] l.s.l.i.version_manager : [localstack-lambda-sns-6a0ec8a6-756a-4de7-8b66-b11b5bd4a7c4] INFO: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Add Entry successfully
  • DynamoDB table:

     awslocal dynamodb query \
    --table-name person \
    --key-condition-expression "id = :id" \
    --expression-attribute-values '{":id":{"N":"000000001"}}'
    {
    "Items": [
    {
    "firstName": {
    "S": "John"
    },
    "lastName": {
    "S": "Doe"
    },
    "address": {
    "S": "Address01"
    },
    "id": {
    "N": "1"
    },
    "age": {
    "N": "26"
    }
    }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
    }