Schedule Lambda Functions Using EventBridge | Serverless
Tags: aws, serverless, javascript, distributedsystems
Published: 2022-10-09 16:54:52

Have you ever wondered 🤔 how you can schedule Lambda functions in an easy way? well in many situations you need lambda functions running some background jobs in scheduled intervals or in specific days

In this blog, will see how to tackle this task using Amazon EventBridge, from which we can easily create rules for our functions and schedule them whenever we want 🏝

The main parts of this article:

1. About AWS Lambda Functions
2. Brief Introduction about Amazon EventBridge
3. About Cron Jobs
4. Examples

## 1. AWS Lambda Functions
AWS Lambda is an event-driven, serverless computing platform. It is a computing service that runs code in response to events and automatically manages the computing resources required by that code

Lambda allows you to focus on your code rather than having to take care of provisioning and maintaining (virtual) machines. It removes the need for such traditional compute services and therefore also reducing the complexity and operation cost. Lambda functions have their limitations but are great for small pieces of isolated tasks

## 2. Amazon EventBridge
EventBridge allows you to build event-driven architectures, which are loosely coupled and distributed. In this article we are going to focus on the scheduling part, however if you want to know more about EventBidge, you can visit this [link] (https://dev.to/aws-builders/amazon-eventbridge-intro-serverless-m02)

## 3. About Cron Jobs (In General)
A cron job is a Linux command used for scheduling tasks to be executed sometime in the future. It is normally used to schedule a job that is executed periodically – for example, to send out a notice every morning

```
* * * * *  command to execute
│ │ │ │ │
│ │ │ │ │
│ │ │ │ └───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, or use names; 7 is Sunday, the same as 0)
│ │ │ └────────── month (1 - 12)
│ │ └─────────────── day of month (1 - 31)
│ └──────────────────── hour (0 - 23)
└───────────────────────── min (0 - 59)
```
For example: 
- `0 7 * * 1` command to execute (Set up a cronjob every once Monday at 7am)
What does Asterisk (*) mean: The asterisk indicates that the cron expression matches for all values of the field. E.g., using an asterisk in the 4th field (month) indicates every month
- `0 0 1 * *` to create a cron job that runs on the first day of month

### Side notes:
- Slash ( / )
Slashes describe increments of ranges. For example 3-59/15 in the 1st field (minutes) indicate the third minute of the hour and every 15 minutes thereafter. 
The form "*/..." is equivalent to the form "first-last/...", that is, an increment over the largest possible range of the field.

- Comma ( , )
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in the 5th field (day of week) means Mondays, Wednesdays and Fridays.

- Hyphen ( - )
Hyphens define ranges. For example, 2000-2010 indicates every year between 2000 and 2010 AD, inclusive.

- Percent ( % )
Percent-signs (%) in the command, unless escaped with backslash (), are changed into newline characters, 
and all data after the first % are sent to the command as standard input.

> 📋 **Note:** For more information and help about Cron Jobs you can visit these site: 
- [https://en.m.wikipedia.org/wiki/Cron] (https://en.m.wikipedia.org/wiki/Cron), 
- [https://www.freeformatter.com/cron-expression-generator-quartz.html] (https://www.freeformatter.com/cron-expression-generator-quartz.html)
- [https://crontab.guru/] (https://crontab.guru/)

To know more about how AWS EventBridge cron job works you can visit this [link] (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html)

## 4. Example
In this part we are going to build 3 simple examples
1- API to schedule Lambda for specified date
2- Directly schedule Lambda for every day job
3- API to list all my Rules

First of all let us create and deploy our Lambda function which are going to execute it later on from the Amazon EventBridge
```javascript
"use strict";

module.exports.backgroundFunction = async (event) => {
  try {
    console.log(event);
    console.log('This is the lambda that will be executed in the background');
  } catch (error) {
    console.log(error);
    return Response(500, {
      message: 'Something went wrong'
    });
  }
};
```
Copy and save the `Arn`, it will be used inside the function that we will add the Targets

### I- Example
In our first example we have two Lambda functions, first one creates the schedule for the EventBridge, and in second one will be executed based on the schedule of the Event. And at the background we will create a Lambda function which will be executed based on the scheduled date (to keep things simple, our Lambda function will just log the data)

- Route:
```yml
scheduleDate:
  handler: src/modules/Schedule/controller/lambda.scheduleDate
  events:
    - http:
        method: get
        path: job/schedule-date
        cors: true
```

- Create a Lambda Function
- Setup a rule in Amazon EventBridge
First you need to create a rule (1), define a pattern and select an event bus (2) assign it a target (our Lambda function) (3)
```javascript
"use strict";
const { listRules, putRule, putTargets, putEvents } = require('../service/eventBridge.service');

module.exports.scheduleDate = async (event) => {
  try {
    console.log(event);
    const body = JSON.parse(event.body);
    console.log('body =>', body);
    const {
      id,
      cron,
    } = body;

    const ruleResult = await putRule(cron);
    console.log('ruleResult =>', ruleResult);
    const targetResult = await putTargets(body);
    console.log('targetResult =>', targetResult);
    const eventResult = await putEvents(body);
    console.log('eventResult =>', eventResult);

    return Response(200, 'Scheduled');
  } catch (error) {
    console.log(error);
    return Response(500, {
      message: 'Something went wrong'
    });
  }
};
```

- EventBridge Helper functions:
```javascript
const AWS = require('aws-sdk');
const eventBridge = new AWS.EventBridge();
const { utcToZonedTime } = require('date-fns-tz');
const { format } = require('date-fns');

const ruleName = 'my_test_rule';

module.exports.putRule = async (scheduledDate) => {
  const utcDate = utcToZonedTime(scheduledDate, 'GMT');
  const ScheduleExpression = `cron(${format(utcDate, 'm')} ${format(utcDate, 'h')} ${format(utcDate, 'd')} ${format(utcDate, 'M')} ? ${format(utcDate, 'yyyy')})`;
  console.log('ScheduleExpression =>', ScheduleExpression);

  const payload = {
    Name: ruleName,
    ScheduleExpression,
  };
  return await eventBridge.putRule(payload).promise();
}

module.exports.putTargets = async (data) => {
  const payload = {
    Rule: ruleName,
    Targets: [
      {
        Arn: process.env.SCHEDULED_LAMBDA_ARN,
        Id: String(data.id),
        Input: JSON.stringify(data),
      }
    ]
  };
  return await eventBridge.putTargets(payload).promise();
}

module.exports.putEvents = async (data) => {
  const payload = {
    Entries: [
      {
        Source: 'custom.lambda',
        DetailType: 'trigger',
        Detail: JSON.stringify(data),
      },
    ],
  };
  return await eventBridge.putEvents(payload).promise();
}
```
> 📋 **Note:** Here I am using `date-fns-tz` & `date-fns` to help me convert my timestamp to cron value, however you are free to use any library that may suit you 🙂

- API body sent:
```json
{
  "id": 12,
  "scheduledDate": 1665225612000
}
```
We can see from the body above that I'm sending scheduledDate value `1665225612000`, which will be converted to `cron(40 10 8 10 ? 2022)`

- Remember to add the permission inside the resources section in `serverless.yml` file (In order to be able to invoke the Lambda from EventBridge)
```yml
resources:
  BackgroundFunctionInvokePermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName: arn:aws:lambda:${env:region}:${env:accountId}:function:${self:service}-${env:stage}-backgroundFunction
      Action: 'lambda:InvokeFunction'
      Principal: 'events.amazonaws.com'
      SourceArn: arn:aws:events:${env:region}:${env:accountId}:rule/my_test_rule
```

> 📋 **Note:** When creating an EventBridge rule with a Lambda function as the target, keep the following in mind: (1- When using the EventBridge console to create the rule, the appropriate permissions are added to the function's resource policy automatically) (2- When using the AWS CLI, SDK, or AWS CloudFormation to create the same rule, you must manually apply the permissions in the resource policy)

- **eventBridge.putRule** - Creates or updates the specified rule. Rules are enabled by default, or based on value of the state. A single rule watches for events from a single event bus
- **eventBridge.putTargets** - Adds the specified targets to the specified rule, or updates the targets if they are already associated with the rule. Targets are the resources that are invoked when a rule is triggered
- **eventBridge.putEvents** - Sends custom events to Amazon EventBridge so that they can be matched to rules

### II- Example
In our second example our Lambda function will be automatically scheduled. For this one we are going to trigger the function every 5 Minutes

- Routes:
```yml
everyFiveMinutesLambda:
  handler: src/modules/Schedule/controller/lambda.everyFiveMinutesLambda
  events:
    - eventBridge:
        schedule: cron(0/5 * * * ? *)
```

- `everyFiveMinutesLambda` Function
```javascript
module.exports.everyFiveMinutesLambda = async (event) => {
  try {
    console.log(event);
    console.log('This Lambda λ function will be executed every Five minutes');

  } catch (error) {
    console.log(error);
    return Response(500, {
      message: 'Something went wrong'
    });
  }
};
```

And that's it now our Lambda function will be executed every five minutes 😎 (To keep it simple I'm just logging out some messages which can be viewed from CloudWatch)

### III- Example
Now let us build a simple API which returns all my Rules created inside EventBridge, if we have successfully created out an scheduled Rule, it should be returned here
- API Route:
```yml
addMessage:
  handler: src/modules/Schedule/controller/lambda.listEventBridgeRules
  events:
    - http:
        method: post
        path: job/list
        cors: true
```

- Lambda Code:
```javascript
"use strict";
const { listRules } = require('../service/eventBridge.service');

module.exports.listEventBridgeRules = async (event) => {
  try {
    console.log(event);
    const body = JSON.parse(event.body);

    const result = await listRules();
    console.log('result =>', result);
    let finalResult = result.Rules;

    finalResult.forEach(function(item) {
      delete item.Arn;
      delete item.EventBusName;
    });

    return Response(200, finalResult);
  } catch (error) {
    console.log(error);
    return Response(500, {
      message: 'Something went wrong'
    });
  }
};
```

- Service helper function:
```javascript
const AWS = require('aws-sdk');
const eventBridge = new AWS.EventBridge();

module.exports.listRules = async () => {
  return await eventBridge.listRules().promise();
}
```

- Response: (Here we should be able to see our previously created rules)
```json
[
    {
        "Name": "my_test_rule",
        "State": "ENABLED",
        "ScheduleExpression": "cron(55 10 8 10 ? 2022)"
    },
    {
        "Name": "sls-try-v1-everyFiveMinutesLambda-rule-1",
        "State": "ENABLED",
        "ScheduleExpression": "cron(0/5 * * * ? *)"
    }
]
```
Voilà 😊 now we have successfully created our scheduled functions, the first one will be executed only one time, while the other every five minutes

## Conclusion
In This article we saw how to schedule a Lambda function using EventBridge. In many scenarios you need this feature, which helps you to tackle real life problems which need cron jobs or scheduled tasks

This article is part of "Messaging with Serverless" series that I've been writing for a while, if you are interested to read other Serverless offerings from AWS, feel free to visit the links below