Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions lambda-durable-eventbridge-cron-nodejs-sam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# EventBridge Cron to Durable Lambda Function

This pattern demonstrates how to trigger a durable Lambda function using EventBridge on a cron schedule. The Lambda function uses the AWS durable execution SDK to implement a multi-step workflow with checkpointing and automatic replay capabilities.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-durable-eventbrdige-cron-nodejs-sam

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Architecture

This architecture consists of a serverless cron job implementation using EventBridge and durable Lambda functions. An EventBridge rule configured with a cron expression triggers the durable Lambda function every 5-minutes. The Lambda function uses the AWS durable execution SDK to implement a multi-step workflow that can span multiple invocations through checkpointing - when `context.wait()` is called, the function suspends execution and creates a checkpoint, then resumes from that point in a subsequent invocation without re-executing previous steps.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
1. Change directory to the pattern directory:
```
cd lambda-durable-eventbridge-cron-nodejs-sam
```
1. From the command line, use AWS SAM to build and deploy the AWS resources for the pattern as specified in the template.yaml file:
```
sam build
sam deploy --guided
```
1. During the prompts:
* Enter a stack name
* Enter the desired AWS Region
* Allow SAM CLI to create IAM roles with the required permissions.

Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults.

1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.

## How it works

This pattern creates:

1. **Durable Orchestrator Lambda Function**: A Nodejs 24.x Lambda function that uses the AWS durable execution SDK to implement a multi-step workflow (invoking 2 Lmabda functions) with automatic checkpointing and replay capabilities.

2. **Data Processor Lambda Function**: An activity function that simulates processing records.

3. **Notification Service Lambda Function**: A final step that simulates sending a summary once processing is complete.

4. **EventBridge Cron Rule**: An EventBridge rule configured with `rate(5 minutes)` that triggers the Lambda function every 5-minutes.

5. **Function Versioning**: The Lambda function uses `AutoPublishAlias: prod` to automatically publish a new version on each deployment and point the `prod` alias to it.

6. **Targeted Invocation**: The EventBridge rule specifically targets the published version via the alias as it is a best practice to use numbered versions or aliases for production durable functions rather than $LATEST.

### Durable Execution Flow

The Lambda function implements a durable workflow with three steps:

1. **Data Processing Step**: Invokes DataProcessor Lambda function with business logic simulating processing data (checkpointed)
2. **Wait Period**: Suspends execution for 10 seconds using `context.wait()` - no compute costs during wait
3. **Notification Service Processing**: Invokes NotificationService Lambda function with business logic simulating sending notifications and returns results

**Execution Pattern**:
- **Invocation 1**: `invoke-data-processor()` runs → checkpoint created → `context.wait()` suspends execution
- **Invocation 2**: `invoke-data-processor()` replays from checkpoint (no re-execution) → wait completes → `invoke-notification-service()` runs → workflow completes

This demonstrates how durable functions can span multiple Lambda invocations while maintaining state and avoiding redundant work through checkpointing.

## Testing

1. After deployment, the EventBridge rule will automatically trigger the Lambda function every 5-minutes.

2. Monitor the function execution in CloudWatch Logs:
```bash
aws logs tail /aws/lambda/DurableOrchestratorFunction --follow
```

3. You should observe the durable execution pattern:
- First invocation: "DataProcessorStep..." followed by suspension
- Second invocation: "NotificationServiceStep..." (DataProcessorStep skipped due to checkpoint)

4. You can also see the durable execution section in the Lambda function console to get a detailed overview of each execution step in the execution.

## Cleanup

1. Delete the stack
```bash
sam delete
```
----
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
25 changes: 25 additions & 0 deletions lambda-durable-eventbridge-cron-nodejs-sam/data-processor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// First Lambda function in the sequence
export const handler = async (event) => {
console.log('Data Processor invoked with:', JSON.stringify(event, null, 2));

const { executionId, triggerTime, task } = event;

// Simulate data processing
const processedRecords = Math.floor(Math.random() * 1000) + 500;

await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate processing time

const result = {
functionName: 'DataProcessorFunction',
executionId,
triggerTime,
task,
recordsProcessed: processedRecords,
status: 'success',
processedAt: new Date().toISOString()
};

console.log('Data processing completed:', result);
return result;
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "data-processor-lambda",
"version": "1.0.0",
"description": "Simple data processor service",
"main": "index.js",
"type": "module",
"keywords": [
"aws",
"lambda"
],
"author": "",
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Main durable function that orchestrates two Lambda functions in sequence

import * as durableSDK from "@aws/durable-execution-sdk-js";
const {withDurableExecution, Duration } = durableSDK;

import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";
const lambdaClient = new LambdaClient({});

export const handler = withDurableExecution(
async (event, context) => {

const executionId = event.id || 'unknown';
const triggerTime = event.time || new Date().toISOString();

// Step 1: Invoke first Lambda function (data processing)
const step1Result = await context.step("invoke-data-processor", async () => {
console.log('DataProcessorStep...');

const command = new InvokeCommand({
FunctionName: "DataProcessorFunction",
InvocationType: "RequestResponse",
Payload: JSON.stringify({
executionId,
triggerTime,
task: "process_data_5minutes"
})
});

const response = await lambdaClient.send(command);
const payload = JSON.parse(new TextDecoder().decode(response.Payload));

return payload;
});

//Lambda will stop executing here and restart in 10 seconds
await context.wait({seconds: 10});

// Step 2: Invoke second Lambda function (notification service)
const step2Result = await context.step("invoke-notification-service", async () => {

console.log('NotificationServiceStep...');

const command = new InvokeCommand({
FunctionName: "NotificationServiceFunction",
InvocationType: "RequestResponse",
Payload: JSON.stringify({
executionId,
triggerTime,
previousStepResult: step1Result,
task: "send_completion_notification",
})
});

const response = await lambdaClient.send(command);
const payload = JSON.parse(new TextDecoder().decode(response.Payload));

return payload;
});

console.log('DurableFunctionExecutionCompleted...');

// Return final result
return {
status: 'completed',
executionId,
triggerTime,
steps: {
dataProcessor: step1Result,
notificationService: step2Result
},
completedAt: new Date().toISOString()
};

}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "nodejs-durable-order-processor",
"version": "1.0.0",
"description": "AWS Lambda durable functions - Node.js orchestrator",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"keywords": [
"aws",
"lambda",
"durable",
"orchestrator"
],
"author": "",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-lambda": "^3.700.0",
"@aws/durable-execution-sdk-js": "^1.0.2"
}
}
63 changes: 63 additions & 0 deletions lambda-durable-eventbridge-cron-nodejs-sam/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"title": "EventBridge Cron to durable Lambda function",
"description": "Create a durable Lambda function triggered by EventBridge on a cron schedule using AWS SAM.",
"language": "Nodejs",
"level": "200",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This sample project demonstrates how to create a durable Lambda function that is triggered by EventBridge on a cron schedule. The Lambda function uses the AWS durable execution SDK to implement a multi-step workflow with automatic checkpointing and replay capabilities.",
"The durable execution pattern allows Lambda functions to span multiple invocations while maintaining state. When the function calls context.wait(), it suspends execution and creates a checkpoint. A subsequent invocation resumes from the checkpoint without re-executing previous steps.",
"This pattern deploys a durable Lambda function with Nodejs 24 runtime, an EventBridge rule with cron schedule, and uses function versioning to ensure the cron trigger targets a published version rather than $LATEST."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-eventbridge-cron-nodejs-sam",
"templateURL": "serverless-patterns/lambda-durable-eventbridge-cron-nodejs-sam",
"projectFolder": "lambda-durable-eventbridge-cron-nodejs-sam",
"templateFile": "template.yaml"
}
},
"resources": {
"bullets": [
{
"text": "AWS Lambda durable functions",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
},
{
"text": "Invoking AWS Lambda durable functions",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html"
},
{
"text": "AWS durable execution SDK for Nodejs",
"link": "https://github.com/aws/aws-durable-execution-sdk-js"
}
]
},
"deploy": {
"text": [
"sam build",
"sam deploy --guided"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>sam delete</code>."
]
},
"authors": [
{
"name": "Anusha Ganapuram",
"image": "https://avatars.githubusercontent.com/u/58950933",
"bio": "Technical Account Manager at AWS with deep expertise in serverless and event-driven solutions. Passionate about building scalable, secure and distributed applications that help organizations modernize their infrastructure and accelerate innovation.",
"linkedin": "anushaganapuram"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

// Second Lambda function in the sequence
export const handler = async (event) => {
console.log('Notification Service invoked with:', JSON.stringify(event, null, 2));

const { executionId, triggerTime, previousStepResult, task } = event;

// Simulate sending notifications
const notificationsSent = Math.floor(Math.random() * 10) + 1;

await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate notification time

const result = {
functionName: 'NotificationServiceFunction',
executionId,
triggerTime,
task,
notificationsSent,
recipientCount: notificationsSent,
previousStepSummary: {
recordsProcessed: previousStepResult.recordsProcessed
},
status: 'success',
notifiedAt: new Date().toISOString()
};

console.log('Notifications sent:', result);
return result;
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "notification-service-lambda",
"version": "1.0.0",
"description": "Simple notification service",
"main": "index.js",
"type": "module",
"keywords": [
"aws",
"lambda"
],
"author": "",
"license": "MIT"
}
Loading