diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/README.md b/lambda-durable-eventbridge-cron-nodejs-sam/README.md new file mode 100644 index 000000000..46a329ca0 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/README.md @@ -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 \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/index.js b/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/index.js new file mode 100644 index 000000000..c3aae3be7 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/index.js @@ -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; +}; + diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/package.json b/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/package.json new file mode 100644 index 000000000..b88be3fb3 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/data-processor/package.json @@ -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" +} \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/index.js b/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/index.js new file mode 100644 index 000000000..d642ece5d --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/index.js @@ -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() + }; + + } +); \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/package.json b/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/package.json new file mode 100644 index 000000000..1728e0714 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/durable-orchestrator/package.json @@ -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" + } +} \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/example-pattern.json b/lambda-durable-eventbridge-cron-nodejs-sam/example-pattern.json new file mode 100644 index 000000000..287beba0a --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/example-pattern.json @@ -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: sam delete." + ] + }, + "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" + } + ] +} \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/index.js b/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/index.js new file mode 100644 index 000000000..269c06b70 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/index.js @@ -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; +}; + diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/package.json b/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/package.json new file mode 100644 index 000000000..a0bb57e34 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/notification-service/package.json @@ -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" +} \ No newline at end of file diff --git a/lambda-durable-eventbridge-cron-nodejs-sam/template.yaml b/lambda-durable-eventbridge-cron-nodejs-sam/template.yaml new file mode 100644 index 000000000..9e4be1245 --- /dev/null +++ b/lambda-durable-eventbridge-cron-nodejs-sam/template.yaml @@ -0,0 +1,90 @@ +#AWS SAM template to deploy the durable function with EventBridge schedule +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: Durable Function with EventBridge Hourly Schedule + +Resources: + # Main Durable Orchestrator Function + DurableOrchestratorFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: DurableOrchestratorFunction + CodeUri: ./durable-orchestrator + Handler: index.handler + Timeout: 900 + MemorySize: 512 + Runtime: nodejs24.x + DurableConfig: + ExecutionTimeout: 900 + RetentionPeriodInDays: 7 + AutoPublishAlias: prod + Environment: + Variables: + DURABLE_EXECUTION_ENABLED: "true" + Policies: + - LambdaInvokePolicy: + FunctionName: !Ref DataProcessorFunction + - LambdaInvokePolicy: + FunctionName: !Ref NotificationServiceFunction + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy + Events: + FiveMinuteSchedule: + Type: Schedule + Properties: + Schedule: "rate(5 minutes)" + Description: Trigger durable function every 5 minutes + Enabled: true + + # Data Processor Function + DataProcessorFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: DataProcessorFunction + CodeUri: ./data-processor + Handler: index.handler + Runtime: nodejs24.x + Timeout: 300 + MemorySize: 256 + + # Notification Service Function + NotificationServiceFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: NotificationServiceFunction + CodeUri: ./notification-service + Handler: index.handler + Runtime: nodejs24.x + Timeout: 300 + MemorySize: 256 + + # CloudWatch Log Groups + DurableOrchestratorLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${DurableOrchestratorFunction}" + RetentionInDays: 7 + + DataProcessorLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${DataProcessorFunction}" + RetentionInDays: 7 + + NotificationServiceLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub "/aws/lambda/${NotificationServiceFunction}" + RetentionInDays: 7 + +Outputs: + DurableOrchestratorFunctionArn: + Description: ARN of the Durable Orchestrator Function + Value: !GetAtt DurableOrchestratorFunction.Arn + + DataProcessorFunctionArn: + Description: ARN of the Data Processor Function + Value: !GetAtt DataProcessorFunction.Arn + + NotificationServiceFunctionArn: + Description: ARN of the Notification Service Function + Value: !GetAtt NotificationServiceFunction.Arn