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
8 changes: 8 additions & 0 deletions aws-lambda-durable-functions-nodejs-saga-pattern/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.aws-sam/
*.pyc
__pycache__/
.DS_Store
samconfig.toml
response.json
node_modules/
package-lock.json
184 changes: 184 additions & 0 deletions aws-lambda-durable-functions-nodejs-saga-pattern/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Saga Pattern with AWS Lambda durable functions (Node.js)

This pattern demonstrates the Saga pattern for distributed transactions using AWS Lambda durable functions in Node.js. It coordinates a multi-step travel booking process (flight, hotel, car) with automatic compensating transactions on failure.

## What is the Saga Pattern?

The Saga pattern manages distributed transactions by breaking them into a sequence of local transactions. Each step can succeed or fail independently, and if any step fails, compensating transactions automatically undo previously completed steps to maintain data consistency.

## Architecture

This implementation uses a single Lambda durable function that:
1. Executes reservation steps sequentially (flight → hotel → car)
2. Tracks completed steps automatically via `context.step()`
3. Implements compensating transactions in reverse order on failure
4. Maintains state across retries without external storage

## Key Features

- **Automatic Checkpointing**: Each `context.step()` creates a checkpoint
- **Fault Tolerance**: Execution resumes from last checkpoint on failure
- **Compensating Transactions**: Automatic rollback in reverse order
- **No External State Store**: Durable functions handle state management
- **Failure Simulation**: Test different failure scenarios

## How It Works

### Success Flow
```
Reserve Flight → Reserve Hotel → Reserve Car → SUCCESS
```

### Failure Flow (e.g., hotel fails)
```
Reserve Flight → Reserve Hotel (FAILS) → Cancel Flight → ROLLBACK COMPLETE
```

### Failure Flow (e.g., car fails)
```
Reserve Flight → Reserve Hotel → Reserve Car (FAILS) → Cancel Hotel → Cancel Flight → ROLLBACK COMPLETE
```

## Deployment

### Prerequisites
- AWS CLI configured
- SAM CLI installed
- Node.js 22.x

### Deploy
```bash
sam build
sam deploy --guided
```

Follow the prompts:
- Stack Name: `saga-pattern-demo`
- AWS Region: Your preferred region
- Confirm changes: Y
- Allow SAM CLI IAM role creation: Y
- Disable rollback: N
- Save arguments to configuration file: Y

## Testing

### Success Case
```bash
aws lambda invoke \
--function-name <FunctionName>:prod \
--invocation-type Event \
--payload '{"tripId":"trip-001","userId":"user-123"}' \
--cli-binary-format raw-in-base64-out \
response.json
```

### Simulate Flight Failure
```bash
aws lambda invoke \
--function-name <FunctionName>:prod \
--invocation-type Event \
--payload '{"tripId":"trip-002","userId":"user-123","simulateFailure":"flight"}' \
--cli-binary-format raw-in-base64-out \
response.json
```

### Simulate Hotel Failure (triggers flight cancellation)
```bash
aws lambda invoke \
--function-name <FunctionName>:prod \
--invocation-type Event \
--payload '{"tripId":"trip-003","userId":"user-123","simulateFailure":"hotel"}' \
--cli-binary-format raw-in-base64-out \
response.json
```

### Simulate Car Failure (triggers hotel and flight cancellation)
```bash
aws lambda invoke \
--function-name <FunctionName>:prod \
--invocation-type Event \
--payload '{"tripId":"trip-004","userId":"user-123","simulateFailure":"car"}' \
--cli-binary-format raw-in-base64-out \
response.json
```

## Viewing Logs

```bash
sam logs --stack-name saga-pattern-demo --tail
```

Or view in CloudWatch Logs console.

## Expected Output

### Success Case
```json
{
"status": "SUCCESS",
"message": "Trip booked successfully",
"tripId": "trip-001",
"userId": "user-123",
"reservations": {
"flight": {
"reservationId": "FL-1234567890",
"from": "SFO",
"to": "NYC",
"date": "2026-03-15",
"status": "CONFIRMED"
},
"hotel": {
"reservationId": "HT-1234567891",
"name": "Grand Hotel NYC",
"checkIn": "2026-03-15",
"checkOut": "2026-03-18",
"status": "CONFIRMED"
},
"car": {
"reservationId": "CR-1234567892",
"type": "SUV",
"pickupDate": "2026-03-15",
"returnDate": "2026-03-18",
"status": "CONFIRMED"
}
}
}
```

### Failure Case (hotel fails)
```json
{
"status": "FAILED",
"message": "Trip booking failed, all reservations rolled back",
"tripId": "trip-003",
"userId": "user-123",
"error": "Hotel reservation failed - no rooms available",
"compensatedServices": ["flight"]
}
```

## How durable functions Enable Saga Pattern

1. **State Management**: `context.step()` automatically creates checkpoints when completing the operation
2. **Idempotency**: Completed steps are executed exactly once, even on retries
3. **Compensation Tracking**: `completedSteps` array tracks what needs rollback
4. **Automatic Recovery**: Failed executions resume from last checkpoint
5. **No External Dependencies**: No DynamoDB or Step Functions needed

## Cleanup

```bash
sam delete --stack-name saga-pattern-demo
```

## Learn More

- [AWS Lambda durable functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html)
- [Saga Pattern](https://microservices.io/patterns/data/saga.html)
- [Distributed Transactions](https://aws.amazon.com/blogs/compute/building-a-serverless-distributed-application-using-a-saga-orchestration-pattern/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright footer is missing


---

© 2026 Amazon Web Services, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"title": "Saga pattern with AWS Lambda durable functions in Node.js",
"description": "Implement the Saga pattern for distributed transactions using AWS Lambda durable functions with automatic compensating transactions",
"language": "Node.js",
"level": "200",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This pattern demonstrates the Saga pattern using AWS Lambda durable functions to coordinate distributed transactions across multiple services.",
"The orchestrator function executes a sequence of reservation steps (flight, hotel, car). Each step is checkpointed automatically using context.step().",
"If any step fails, compensating transactions execute in reverse order to rollback all completed operations, ensuring data consistency.",
"The durable function maintains execution state without requiring external storage like DynamoDB or Step Functions."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/aws-lambda-durable-functions-nodejs-saga-pattern",
"templateURL": "serverless-patterns/aws-lambda-durable-functions-nodejs-saga-pattern",
"projectFolder": "aws-lambda-durable-functions-nodejs-saga-pattern",
"templateFile": "template.yaml"
}
},
"resources": {
"bullets": [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include a link to the Lambda Durable Execution SDK

{
"text": "AWS Lambda durable functions",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
},
{
"text": "Lambda Durable Execution SDK",
"link": "https://github.com/aws/aws-durable-execution-sdk-js"
},
{
"text": "Saga Pattern",
"link": "https://microservices.io/patterns/data/saga.html"
},
{
"text": "Building Serverless Distributed Applications with Saga",
"link": "https://aws.amazon.com/blogs/compute/building-a-serverless-distributed-application-using-a-saga-orchestration-pattern/"
}
]
},
"deploy": {
"text": [
"sam build",
"sam deploy --guided"
]
},
"testing": {
"text": [
"See the README in the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"<code>sam delete --stack-name saga-pattern-demo</code>"
]
},
"authors": [
{
"name": "Surya Sai D",
"image": "",
"bio": "Surya works as a Technical Account Manager at AWS. He is an expert in Serverless frameworks and Event Driven Architectures. Surya is also passionate on technical writing and has contributed to AWS blogs and other Open Source Content.",
"linkedin": "surya-sai-d-64920416a"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { withDurableExecution } = require('@aws/durable-execution-sdk-js');

exports.handler = withDurableExecution(async (event, context) => {
context.logger.info('=== Saga Orchestrator Started ===');
context.logger.info('Input:', JSON.stringify(event));

const { tripId, userId, simulateFailure } = event;
const completedSteps = [];

try {
// Step 1: Reserve Flight
const flight = await context.step('reserveFlight', async () => {
context.logger.info('Reserving flight...');
if (simulateFailure === 'flight') {
throw new Error('Flight reservation failed - no availability');
}
const reservationId = `FL-${Date.now()}`;
context.logger.info(`Flight reserved: ${reservationId}`);
return { reservationId, from: 'SFO', to: 'NYC', date: '2026-03-15', status: 'CONFIRMED' };
});
completedSteps.push({ service: 'flight', data: flight });

// Step 2: Reserve Hotel
const hotel = await context.step('reserveHotel', async () => {
context.logger.info('Reserving hotel...');
if (simulateFailure === 'hotel') {
throw new Error('Hotel reservation failed - no rooms available');
}
const reservationId = `HT-${Date.now()}`;
context.logger.info(`Hotel reserved: ${reservationId}`);
return { reservationId, name: 'Grand Hotel NYC', checkIn: '2026-03-15', checkOut: '2026-03-18', status: 'CONFIRMED' };
}, { retry: { maxAttempts: 1 } });
completedSteps.push({ service: 'hotel', data: hotel });

// Step 3: Reserve Car
const car = await context.step('reserveCar', async () => {
context.logger.info('Reserving car...');
if (simulateFailure === 'car') {
throw new Error('Car reservation failed - no vehicles available');
}
const reservationId = `CR-${Date.now()}`;
context.logger.info(`Car reserved: ${reservationId}`);
return { reservationId, type: 'SUV', pickupDate: '2026-03-15', returnDate: '2026-03-18', status: 'CONFIRMED' };
}, { retry: { maxAttempts: 1 } });
completedSteps.push({ service: 'car', data: car });

context.logger.info('=== All Reservations Completed Successfully ===');
return {
status: 'SUCCESS',
message: 'Trip booked successfully',
tripId,
userId,
reservations: { flight, hotel, car }
};

} catch (error) {
context.logger.error('=== Saga Failed - Initiating Compensating Transactions ===');
context.logger.error('Error:', error.message);

// Execute compensating transactions in REVERSE order
for (let i = completedSteps.length - 1; i >= 0; i--) {
const step = completedSteps[i];
await context.step(`cancel_${step.service}`, async () => {
context.logger.info(`Cancelling ${step.service}: ${step.data.reservationId}`);
// Simulate cancellation logic
return { reservationId: step.data.reservationId, status: 'CANCELLED' };
});
}

context.logger.info('=== All Compensating Transactions Completed ===');
return {
status: 'FAILED',
message: 'Trip booking failed, all reservations rolled back',
tripId,
userId,
error: error.message,
compensatedServices: completedSteps.map(s => s.service)
};
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "saga-orchestrator",
"version": "1.0.0",
"dependencies": {
"@aws/durable-execution-sdk-js": "^1.0.0"
}
}
29 changes: 29 additions & 0 deletions aws-lambda-durable-functions-nodejs-saga-pattern/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Saga pattern implementation using AWS Lambda durable functions in Node.js

Resources:
SagaOrchestratorFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/orchestrator/
Handler: index.handler
Runtime: nodejs22.x
Timeout: 900
MemorySize: 512
AutoPublishAlias: prod
DurableConfig:
ExecutionTimeout: 3600
RetentionPeriodInDays: 7
Environment:
Variables:
LOG_LEVEL: INFO

Outputs:
SagaOrchestratorFunction:
Description: Saga Orchestrator Lambda Function ARN
Value: !GetAtt SagaOrchestratorFunction.Arn

SagaOrchestratorFunctionAlias:
Description: Saga Orchestrator Function Alias (use this for invocation)
Value: !Sub '${SagaOrchestratorFunction}:prod'