DEV Community

Cover image for Serverless Scheduled Reporting Service with AWS EventBridge, Lambda, SNS, and CDK
Jimmy for AWS Community Builders

Posted on • Updated on

Serverless Scheduled Reporting Service with AWS EventBridge, Lambda, SNS, and CDK

Have you ever thought of building a simple scheduled reporting service without the need to manage a server and only pay per use (which in our case only runs 30 times a month)?

This scheduling service will send a daily total sales report to your email like this.
sales notification

So in this article, we will learn how to build those services by utilizing several AWS services like Amazon EventBridge, AWS Lambda, Amazon SNS and deploy it with AWS CDK.

Follow me for more

FullStack Saiyan | Twitter, Instagram, Facebook | Linktree

Share about Web, Mobile, Backend, Frontend, and Cloud Computing.

favicon linktr.ee

EventBridge (CloudWatch Events)

amazon eventbridge scheduler
EventBridge (CloudWatch Events) is a serverless event bus that makes it easy to connect your applications and services to data sources and other AWS services. It enables you to build event-driven architectures that respond automatically to state changes in your AWS resources.

EventBridge provides a unified view of all your events, regardless of where they originate. You can create rules to match events and route them to targets such as AWS Lambda functions, Amazon Kinesis streams, and Amazon Simple Notification Service (SNS) topics. This allows you to automate your workflows and respond to events in real-time.

AWS Lambda

aws lambda
AWS Lambda is a serverless computing service offered by Amazon Web Services (AWS). It allows developers to run code without provisioning or managing servers, as the underlying infrastructure is managed by AWS. With Lambda, developers can focus solely on writing and deploying their code, without worrying about scaling, capacity planning, or server management.

Lambda functions can be triggered by various events, such as changes to an S3 bucket, an HTTP request, or a message from an Amazon Kinesis stream. Once triggered, the function executes in a sandboxed environment, which runs on top of Amazon Elastic Container Service (ECS) containers. This environment provides isolation between functions and ensures that they do not interfere with each other.

Amazon Simple Notification Service (SNS)

amazon sns
Amazon Simple Notification Service (SNS) is a fully managed messaging service provided by Amazon Web Services (AWS). It enables developers to send individual messages or large-scale notifications to multiple subscribers or clients, such as mobile devices, email addresses, and HTTP endpoints.

With SNS, developers can fan out messages to a large number of recipients without having to worry about the underlying infrastructure, scaling, or delivery guarantees. The service takes care of distributing messages to the specified subscribers, handling duplication, throttling, and deliverability.

AWS Cloud Development Kit (CDK)

aws cdk

AWS CDK (Cloud Development Kit) is an open-source software development framework created by Amazon Web Services (AWS). It allows developers to define cloud infrastructure and applications in code, rather than manually configuring them through the AWS Management Console or CLI.

CDK provides a programmatic way to define infrastructure using familiar programming languages like TypeScript, JavaScript, Python, and Java. It offers a set of libraries and constructs that enable developers to describe the desired state of their infrastructure, and then provisions and manages it automatically.

Prerequisites

Table of Contents

Create Project

  • We will start by creating a folder.
mkdir cdk-scheduled-reporting && cd cdk-scheduled-reporting
Enter fullscreen mode Exit fullscreen mode
  • Then initialize our CDK project
cdk init app --language javascript
Enter fullscreen mode Exit fullscreen mode

Create Lambda handler

We will create our Lambda handler with the logic to collect the sales data and send it to Amazon SNS.

  • Create src folder
  • Create index.js inside src folder
touch src/index.js
Enter fullscreen mode Exit fullscreen mode
  • We will simulate our sales data by getting dummy cart data from https://dummyjson.com/carts REST API server.
  • So, start typing the following code into src/index.js
const AWS = require("aws-sdk");
const sns = new AWS.SNS({ apiVersion: "2010-03-31" });

exports.handler = async (event) => {
  try {
    const data = await sns
      .publish({
        Subject: "Daily Sales Report",
        Message: `Your today's total sales is $ ${await fetchTotalSales()}`,
        TopicArn: process.env.TOPIC_ARN,
      })
      .promise();
    console.log("data", data);
  } catch (err) {
    console.log("err", err);
  }
  return { message: "Successfully executed" };
};

async function fetchTotalSales() {
  const fetch = await import("node-fetch");
  return fetch
    .default("https://dummyjson.com/carts")
    .then((response) => response.json())
    .then((data) => {
      let total = 0;
      total = data.carts.reduce((acc, item) => {
        return acc + item.discountedTotal;
      }, 0);
      console.log(total);
      return total.toLocaleString();
    });
}
Enter fullscreen mode Exit fullscreen mode

Create Stack

  • Go to lib/cdk-scheduled-reporting-stack.js
  • Our lib/cdk-scheduled-reporting-stack.js will look like this for now.
const { Stack, Duration } = require("aws-cdk-lib");

class CdkScheduledReportingStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);
  }
}

module.exports = { CdkScheduledReportingStack };
Enter fullscreen mode Exit fullscreen mode

Import modules

To create our stack, we will import several modules from aws-cdk-lib and path

const { Stack, Duration, CfnParameter } = require("aws-cdk-lib");
const lambda = require("aws-cdk-lib/aws-lambda");
const event = require("aws-cdk-lib/aws-events");
const target = require("aws-cdk-lib/aws-events-targets");
const sns = require("aws-cdk-lib/aws-sns");
const subscription = require("aws-cdk-lib/aws-sns-subscriptions");

const path = require("path");
Enter fullscreen mode Exit fullscreen mode

Create Parameter

We will create an EmailAddress parameter of type string, which will be used to input an email address to which a sales report will be sent.

const emailAddress = new CfnParameter(this, "EmailAddress", {
  type: "String",
  description: "Email address to send sales report",
});
Enter fullscreen mode Exit fullscreen mode

Create SNS Topic

Next, we will define Amazon SNS topic and subscribe to our EmailAddress so that when a message is published to the SNS topic, it will be sent to the email address specified in the EmailAddress parameter.

const topic = new sns.Topic(this, "SalesReportTopic", {
  displayName: "ScheduledReportingTopic",
});

topic.addSubscription(
  new subscription.EmailSubscription(emailAddress.valueAsString)
);
Enter fullscreen mode Exit fullscreen mode

Create Lambda Function

Now, we will create our Lambda function and load our handler from src folder.

const salesReportFunction = new lambda.Function(this, "SalesReportFunction", {
  runtime: lambda.Runtime.NODEJS_14_X,
  code: lambda.Code.fromAsset(path.join(__dirname, "../src")),
  handler: "index.handler",
  timeout: Duration.seconds(300),
  environment: {
    TOPIC_ARN: topic.topicArn,
  },
});
Enter fullscreen mode Exit fullscreen mode

Permission to publish message to SNS Topic

Next, we will give the salesReportFunction AWS Lambda function the necessary permissions to publish messages to the topic SNS topic.
This means that the Lambda function can send messages to the SNS topic, which will then distribute the messages to all of the topic's subscribers.

topic.grantPublish(salesReportFunction);
Enter fullscreen mode Exit fullscreen mode

Create EventBridge Schedule Rule

Now, we will create an EventBrdige Schedule Rule that will trigger every day at 11:00 AM UTC or 06:00 PM GMT+7

const rule = new event.Rule(this, "Rule", {
  schedule: event.Schedule.expression("cron(0 11 * * ? *)"),
});
Enter fullscreen mode Exit fullscreen mode

Trigger Lambda from EventBridge

Add the salesReportFunction Lambda function as a target to the rule EventBridge Schedule rule. This means that when the rule is triggered according to its schedule, it will invoke the salesReportFunction Lambda function.

rule.addTarget(new target.LambdaFunction(salesReportFunction));
Enter fullscreen mode Exit fullscreen mode

Complete Stack Code

Finally, our complete stack code will look like this.

const { Stack, Duration, CfnParameter } = require("aws-cdk-lib");
const lambda = require("aws-cdk-lib/aws-lambda");
const event = require("aws-cdk-lib/aws-events");
const target = require("aws-cdk-lib/aws-events-targets");
const sns = require("aws-cdk-lib/aws-sns");
const subscription = require("aws-cdk-lib/aws-sns-subscriptions");

const path = require("path");

class CdkScheduledReportingStack extends Stack {
  constructor(scope, id, props) {
    super(scope, id, props);

    const emailAddress = new CfnParameter(this, "EmailAddress", {
      type: "String",
      description: "Email address to send sales report",
    });

    const topic = new sns.Topic(this, "SalesReportTopic", {
      displayName: "ScheduledReportingTopic",
    });

    topic.addSubscription(
      new subscription.EmailSubscription(emailAddress.valueAsString)
    );

    const salesReportFunction = new lambda.Function(
      this,
      "SalesReportFunction",
      {
        runtime: lambda.Runtime.NODEJS_14_X,
        code: lambda.Code.fromAsset(path.join(__dirname, "../src")),
        handler: "index.handler",
        timeout: Duration.seconds(300),
        environment: {
          TOPIC_ARN: topic.topicArn,
        },
      }
    );

    topic.grantPublish(salesReportFunction);

    const rule = new event.Rule(this, "Rule", {
      schedule: event.Schedule.expression("cron(0 11 * * ? *)"),
    });

    rule.addTarget(new target.LambdaFunction(salesReportFunction));
  }
}

module.exports = { CdkScheduledReportingStack };
Enter fullscreen mode Exit fullscreen mode

Deploy Stack

To deploy our CDK Stack, we can run this command in our terminal.

cdk deploy --parameters EmailAddress=youremail@email.com
Enter fullscreen mode Exit fullscreen mode

You can define your own email address by changing youremail@email.com value.

CDK will start synthesizing our stack like this

✨  Synthesis time: 14.44s

CdkScheduledReportingStack:  start: Building f3a91fb0ff1605e49b4b00aa223098d7a9a5307e98027f77aecc36bc05a4c3e7:current_account-current_region
CdkScheduledReportingStack:  success: Built f3a91fb0ff1605e49b4b00aa223098d7a9a5307e98027f77aecc36bc05a4c3e7:current_account-current_region
CdkScheduledReportingStack:  start: Publishing f3a91fb0ff1605e49b4b00aa223098d7a9a5307e98027f77aecc36bc05a4c3e7:current_account-current_region
CdkScheduledReportingStack:  start: Building 517f907ce2318aa1a929b43bc1a0247ed509c92576c23a0a8aadfe1677f2ebd7:current_account-current_region
CdkScheduledReportingStack:  success: Built 517f907ce2318aa1a929b43bc1a0247ed509c92576c23a0a8aadfe1677f2ebd7:current_account-current_region
CdkScheduledReportingStack:  start: Publishing 517f907ce2318aa1a929b43bc1a0247ed509c92576c23a0a8aadfe1677f2ebd7:current_account-current_region
CdkScheduledReportingStack:  success: Published 517f907ce2318aa1a929b43bc1a0247ed509c92576c23a0a8aadfe1677f2ebd7:current_account-current_region
CdkScheduledReportingStack:  success: Published f3a91fb0ff1605e49b4b00aa223098d7a9a5307e98027f77aecc36bc05a4c3e7:current_account-current_region
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬────────────────────────────────────────┬────────┬───────────────────────┬────────────────────────────────────────┬───────────────────────────────────────────────┐
│   │ Resource                               │ Effect │ Action                │ Principal                              │ Condition                                     │
├───┼────────────────────────────────────────┼────────┼───────────────────────┼────────────────────────────────────────┼───────────────────────────────────────────────┤
│ + │ ${SalesReportFunction.Arn}             │ Allow  │ lambda:InvokeFunction │ Service:events.amazonaws.com           │ "ArnLike": {                                  │
│   │                                        │        │                       │                                        │   "AWS:SourceArn": "${Rule.Arn}"              │
│   │                                        │        │                       │                                        │ }                                             │
├───┼────────────────────────────────────────┼────────┼───────────────────────┼────────────────────────────────────────┼───────────────────────────────────────────────┤
│ + │ ${SalesReportFunction/ServiceRole.Arn} │ Allow  │ sts:AssumeRole        │ Service:lambda.amazonaws.com           │                                               │
├───┼────────────────────────────────────────┼────────┼───────────────────────┼────────────────────────────────────────┼───────────────────────────────────────────────┤
│ + │ ${SalesReportTopic}                    │ Allow  │ sns:Publish           │ AWS:${SalesReportFunction/ServiceRole} │                                               │
└───┴────────────────────────────────────────┴────────┴───────────────────────┴────────────────────────────────────────┴───────────────────────────────────────────────┘
IAM Policy Changes
┌───┬────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                           │ Managed Policy ARN                                                             │
├───┼────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${SalesReportFunction/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?
Enter fullscreen mode Exit fullscreen mode

After confirm the deployment, CDK will start deploying our service

CdkScheduledReportingStack: deploying... [1/1]
CdkScheduledReportingStack: creating CloudFormation changeset...
CdkScheduledReportingStack | 0/10 | 11:16:56 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:1 (SalesReportTopicTokenSubscription199EB8BBD)
CdkScheduledReportingStack | 0/10 | 11:16:56 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:2 (SalesReportTopicTokenSubscription246354DF1)
CdkScheduledReportingStack | 0/10 | 11:16:46 PM | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | CdkScheduledReportingStack User Initiated
CdkScheduledReportingStack | 0/10 | 11:16:51 PM | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | CdkScheduledReportingStack User Initiated
CdkScheduledReportingStack | 0/10 | 11:16:55 PM | CREATE_IN_PROGRESS   | AWS::SNS::Topic         | SalesReportTopic (SalesReportTopic505419E0)
CdkScheduledReportingStack | 0/10 | 11:16:55 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata      | CDKMetadata/Default (CDKMetadata)
CdkScheduledReportingStack | 0/10 | 11:16:55 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | SalesReportFunction/ServiceRole (SalesReportFunctionServiceRole3A32E4C8)
CdkScheduledReportingStack | 0/10 | 11:16:56 PM | CREATE_IN_PROGRESS   | AWS::SNS::Topic         | SalesReportTopic (SalesReportTopic505419E0) Resource creation Initiated
CdkScheduledReportingStack | 0/10 | 11:16:56 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata      | CDKMetadata/Default (CDKMetadata) Resource creation Initiated
CdkScheduledReportingStack | 1/10 | 11:16:56 PM | CREATE_COMPLETE      | AWS::SNS::Topic         | SalesReportTopic (SalesReportTopic505419E0)
CdkScheduledReportingStack | 2/10 | 11:16:56 PM | CREATE_COMPLETE      | AWS::CDK::Metadata      | CDKMetadata/Default (CDKMetadata)
CdkScheduledReportingStack | 2/10 | 11:16:56 PM | CREATE_IN_PROGRESS   | AWS::IAM::Role          | SalesReportFunction/ServiceRole (SalesReportFunctionServiceRole3A32E4C8) Resource creation Initiated
CdkScheduledReportingStack | 2/10 | 11:16:57 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:1 (SalesReportTopicTokenSubscription199EB8BBD) Resource creation Initiated
CdkScheduledReportingStack | 3/10 | 11:16:57 PM | CREATE_COMPLETE      | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:1 (SalesReportTopicTokenSubscription199EB8BBD)
CdkScheduledReportingStack | 3/10 | 11:16:57 PM | CREATE_IN_PROGRESS   | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:2 (SalesReportTopicTokenSubscription246354DF1) Resource creation Initiated
CdkScheduledReportingStack | 4/10 | 11:16:57 PM | CREATE_COMPLETE      | AWS::SNS::Subscription  | SalesReportTopic/TokenSubscription:2 (SalesReportTopicTokenSubscription246354DF1)
CdkScheduledReportingStack | 5/10 | 11:17:12 PM | CREATE_COMPLETE      | AWS::IAM::Role          | SalesReportFunction/ServiceRole (SalesReportFunctionServiceRole3A32E4C8)
CdkScheduledReportingStack | 5/10 | 11:17:13 PM | CREATE_IN_PROGRESS   | AWS::IAM::Policy        | SalesReportFunction/ServiceRole/DefaultPolicy (SalesReportFunctionServiceRoleDefaultPolicy1B643419)
CdkScheduledReportingStack | 5/10 | 11:17:15 PM | CREATE_IN_PROGRESS   | AWS::IAM::Policy        | SalesReportFunction/ServiceRole/DefaultPolicy (SalesReportFunctionServiceRoleDefaultPolicy1B643419) Resource creation Initiated
CdkScheduledReportingStack | 6/10 | 11:17:31 PM | CREATE_COMPLETE      | AWS::IAM::Policy        | SalesReportFunction/ServiceRole/DefaultPolicy (SalesReportFunctionServiceRoleDefaultPolicy1B643419)
CdkScheduledReportingStack | 6/10 | 11:17:33 PM | CREATE_IN_PROGRESS   | AWS::Lambda::Function   | SalesReportFunction (SalesReportFunctionAFEC00DA)
CdkScheduledReportingStack | 6/10 | 11:17:35 PM | CREATE_IN_PROGRESS   | AWS::Lambda::Function   | SalesReportFunction (SalesReportFunctionAFEC00DA) Resource creation Initiated
CdkScheduledReportingStack | 7/10 | 11:17:40 PM | CREATE_COMPLETE      | AWS::Lambda::Function   | SalesReportFunction (SalesReportFunctionAFEC00DA)
CdkScheduledReportingStack | 7/10 | 11:17:41 PM | CREATE_IN_PROGRESS   | AWS::Events::Rule       | Rule (Rule4C995B7F)
CdkScheduledReportingStack | 7/10 | 11:17:42 PM | CREATE_IN_PROGRESS   | AWS::Events::Rule       | Rule (Rule4C995B7F) Resource creation Initiated
7/10 Currently in progress: CdkScheduledReportingStack, Rule4C995B7F
CdkScheduledReportingStack | 8/10 | 11:18:43 PM | CREATE_COMPLETE      | AWS::Events::Rule       | Rule (Rule4C995B7F)
CdkScheduledReportingStack | 8/10 | 11:18:44 PM | CREATE_IN_PROGRESS   | AWS::Lambda::Permission | Rule/AllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CE (RuleAllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CEB53F3E4B)
CdkScheduledReportingStack | 8/10 | 11:18:45 PM | CREATE_IN_PROGRESS   | AWS::Lambda::Permission | Rule/AllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CE (RuleAllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CEB53F3E4B) Resource creation Initiated
CdkScheduledReportingStack | 9/10 | 11:18:45 PM | CREATE_COMPLETE      | AWS::Lambda::Permission | Rule/AllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CE (RuleAllowEventRuleCdkScheduledReportingStackSalesReportFunctionA41A35CEB53F3E4B)
CdkScheduledReportingStack | 10/10 | 11:18:46 PM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CdkScheduledReportingStack

 ✅  CdkScheduledReportingStack

✨  Deployment time: 125.83s
Outputs:
CdkScheduledResportingStack.LambdaFunction = CdkScheduledResportingSta-SalesReportFunctionAFEC0-ZnCpb5NTiaaq
Stack ARN:
arn:aws:cloudformation:ap-southeast-1:00000000000:stack/CdkScheduledReportingStack/60c2ae80-7f1b-11ee-9fa0-0abc96bce958

✨  Total time: 140.27s
Enter fullscreen mode Exit fullscreen mode

Pay attention to the outputs of our Lambda Function name CdkScheduledResportingSta-SalesReportFunctionAFEC0-ZnCpb5NTiaaq. We will use it later to test our Lambda function.

After the deployment is complete, you will receive a topic subscription confirmation like this that you need to confirm so that Amazon SNS can send messages to your email.
Subscription Confirmation

Testing

To see whether our Lambda function is working or not, we have 3 options :

  • Wait until the EventBridge schedule triggers our Lambda function at 11:00 AM UTC or 06:00 PM GMT+7.
  • Trigger it manually through AWS Management Console.
  • Trigger it manually through AWS CLI.

I will show you the last option, by triggering our Lambda function CdkScheduledResportingSta-SalesReportFunctionAFEC0-ZnCpb5NTiaaq manually through AWS CLI. You can do it by running the following command into your terminal.

aws lambda invoke --function-name CdkScheduledResportingSta-SalesReportFunctionAFEC0-ZnCpb5NTiaaq --invocation-type Event response.json
Enter fullscreen mode Exit fullscreen mode

After running the above command, you will receive your sales notification in your email like this
sales notification

By receiving the above email, that means our service is deployed and working perfectly.

wohoo

Conclusion

So in this article, we successfully learn that we can deploy our serverless scheduled reporting service with AWS EventBridge, Lambda, SNS, and CDK at ease without the need to manage a server and only pay per use.

Check out my previous post

Top comments (1)

Collapse
 
az_codez profile image
Asrin

Nice! I'm going to try this!