Skip to content

Commit 123a375

Browse files
authored
Merge pull request #2954 from 4D54/lambda-durable-hitl-python-sam
Add Lambda Durable Functions with Human-in-the-Loop pattern
2 parents 9e3265a + cd83615 commit 123a375

14 files changed

Lines changed: 1932 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual environments
24+
venv/
25+
ENV/
26+
env/
27+
.venv
28+
29+
# Testing
30+
.pytest_cache/
31+
.coverage
32+
htmlcov/
33+
.hypothesis/
34+
*.cover
35+
.tox/
36+
37+
# IDEs
38+
.vscode/
39+
.idea/
40+
*.swp
41+
*.swo
42+
*~
43+
44+
# AWS SAM
45+
.aws-sam/
46+
samconfig.toml
47+
48+
# Logs
49+
*.log
50+
51+
# OS
52+
.DS_Store
53+
Thumbs.db
54+
55+
# Environment variables
56+
.env
57+
.env.local
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# AWS Lambda durable functions to DynamoDB with Human-in-the-Loop
2+
3+
This pattern demonstrates how to implement Lambda durable functions with Human-in-the-Loop (HITL) approval workflows. The workflow pauses execution, waits for human approval via callback, and resumes based on the decision while maintaining state across the pause/resume cycle.
4+
5+
Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-durable-hitl-python-sam
6+
7+
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.
8+
9+
## Requirements
10+
11+
* [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.
12+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
13+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
14+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
15+
* [Docker](https://docs.docker.com/get-docker/) installed (for building Lambda container images)
16+
* [Python 3.13](https://www.python.org/downloads/) or later
17+
18+
## Deployment Instructions
19+
20+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
21+
```
22+
git clone https://github.com/aws-samples/serverless-patterns
23+
```
24+
1. Change directory to the pattern directory:
25+
```
26+
cd lambda-durable-hitl-python-sam
27+
```
28+
1. From the command line, use AWS SAM to build the application:
29+
```
30+
sam build
31+
```
32+
1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yaml file:
33+
```
34+
sam deploy --guided
35+
```
36+
1. During the prompts:
37+
* Enter a stack name
38+
* Enter the desired AWS Region
39+
* Enter the ApprovalTimeoutSeconds parameter (default: 300 seconds)
40+
* Allow SAM CLI to create IAM roles with the required permissions.
41+
42+
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.
43+
44+
1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.
45+
46+
## How it works
47+
48+
This pattern implements a Human-in-the-Loop approval workflow using Lambda durable functions:
49+
50+
1. **Workflow Lambda** creates an approval request in DynamoDB and sends an SNS notification to approvers
51+
2. The workflow pauses execution using `callback.result()` and waits for a callback
52+
3. **Approval API Lambda** processes the approval decision and calls the Lambda durable execution callback API
53+
4. The workflow resumes automatically when the callback is invoked and completes with the decision
54+
55+
The pattern uses the AWS Durable Execution SDK for Python with the `@durable_execution` decorator to maintain state across the pause/resume cycle. The callback pattern ensures no compute charges while waiting for human decisions.
56+
57+
### Architecture Components
58+
59+
- **Workflow Lambda**: Orchestrates the approval workflow using Lambda durable functions SDK with callback pattern
60+
- **Approval API Lambda**: Processes approval/rejection decisions and invokes the callback API to resume the workflow
61+
- **DynamoDB Table**: Stores approval request state including callback tokens, document details, and timestamps
62+
- **SNS Topic**: Sends notifications to approvers when new approval requests are created
63+
64+
## Testing
65+
66+
### Set Environment Variables
67+
68+
```bash
69+
export AWS_DEFAULT_REGION=us-east-1
70+
export STACK_NAME=<your-stack-name>
71+
72+
# Get function names from CloudFormation outputs
73+
export WORKFLOW_FUNCTION=$(aws cloudformation describe-stacks \
74+
--stack-name $STACK_NAME \
75+
--query 'Stacks[0].Outputs[?OutputKey==`WorkflowFunctionName`].OutputValue' \
76+
--output text)
77+
78+
export APPROVAL_API_FUNCTION=$(aws cloudformation describe-stacks \
79+
--stack-name $STACK_NAME \
80+
--query 'Stacks[0].Outputs[?OutputKey==`ApprovalApiFunctionName`].OutputValue' \
81+
--output text)
82+
```
83+
84+
### Invoke the Workflow
85+
86+
```bash
87+
# Invoke workflow with a document approval request
88+
aws lambda invoke \
89+
--function-name $WORKFLOW_FUNCTION \
90+
--cli-binary-format raw-in-base64-out \
91+
--payload '{"document_id":"doc-123","document_name":"Q4 Budget Proposal","requester":"user@example.com"}' \
92+
response.json
93+
94+
# Check response
95+
cat response.json
96+
```
97+
98+
### List Pending Approvals
99+
100+
```bash
101+
# Scan DynamoDB for pending approval requests
102+
aws dynamodb scan \
103+
--table-name $STACK_NAME-ApprovalRequests \
104+
--filter-expression "#status = :pending" \
105+
--expression-attribute-names '{"#status":"status"}' \
106+
--expression-attribute-values '{":pending":{"S":"pending"}}' \
107+
--max-items 10
108+
```
109+
110+
### Submit Approval Decision
111+
112+
```bash
113+
# Get the approval_id from the DynamoDB scan output above
114+
115+
# Approve the request
116+
aws lambda invoke \
117+
--function-name $APPROVAL_API_FUNCTION \
118+
--cli-binary-format raw-in-base64-out \
119+
--payload '{"action":"decide","approval_id":"<APPROVAL_ID>","decision":"approved","approver":"test-approver","comments":"Looks good"}' \
120+
approval_response.json
121+
122+
# Check response
123+
cat approval_response.json
124+
```
125+
126+
### Verify Workflow Completion
127+
128+
```bash
129+
# Check DynamoDB to verify status changed to approved
130+
aws dynamodb get-item \
131+
--table-name $STACK_NAME-ApprovalRequests \
132+
--key '{"approval_id":{"S":"<APPROVAL_ID>"}}'
133+
134+
# Check CloudWatch Logs for workflow completion
135+
aws logs tail /aws/lambda/$WORKFLOW_FUNCTION --follow
136+
```
137+
138+
Expected output: The workflow should complete and return the approval decision. The DynamoDB item should show status as "approved" with the approver's comments and timestamp.
139+
140+
A successful test shows these log messages:
141+
- Workflow logs: `Callback created``Approval request created``SNS notification sent``Waiting for approval callback`
142+
- After approval: `Callback received, workflow resuming``Workflow completed successfully` with decision "approved"
143+
144+
## Cleanup
145+
146+
1. Delete the stack
147+
```bash
148+
sam delete
149+
```
150+
1. Confirm the stack has been deleted
151+
```bash
152+
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'$STACK_NAME')].StackStatus"
153+
```
154+
155+
----
156+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
157+
158+
SPDX-License-Identifier: MIT-0
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"title": "Lambda durable functions with Human-in-the-Loop",
3+
"description": "Demonstrates Lambda durable functions with human approval workflow using Python 3.13, DynamoDB, and SNS",
4+
"language": "Python",
5+
"level": "300",
6+
"framework": "SAM",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern demonstrates how to pause Lambda execution, wait for human approval, and resume using the Lambda durable functions SDK.",
11+
"The Workflow Lambda creates an approval request in DynamoDB and polls for decisions using durable waits (no compute charges during waits).",
12+
"An SNS notification is sent to approvers, who can submit their decision via the Approval API Lambda function.",
13+
"The Workflow Lambda detects the decision during polling and resumes execution with the approval result.",
14+
"The pattern includes timeout handling, status tracking, and a complete audit trail of all approval decisions."
15+
]
16+
},
17+
"gitHub": {
18+
"template": {
19+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-hitl-python-sam",
20+
"templateURL": "serverless-patterns/lambda-durable-hitl-python-sam",
21+
"projectFolder": "lambda-durable-hitl-python-sam",
22+
"templateFile": "template.yaml"
23+
}
24+
},
25+
"resources": {
26+
"bullets": [
27+
{
28+
"text": "Lambda Durable Functions Documentation",
29+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
30+
},
31+
{
32+
"text": "AWS SAM Documentation",
33+
"link": "https://docs.aws.amazon.com/serverless-application-model/"
34+
},
35+
{
36+
"text": "DynamoDB Best Practices",
37+
"link": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html"
38+
},
39+
{
40+
"text": "Amazon SNS Documentation",
41+
"link": "https://docs.aws.amazon.com/sns/"
42+
}
43+
]
44+
},
45+
"deploy": {
46+
"text": [
47+
"sam build",
48+
"sam deploy --guided"
49+
]
50+
},
51+
"testing": {
52+
"text": [
53+
"See the README in the GitHub repo for detailed testing instructions.",
54+
"Test the approval workflow using AWS CLI:",
55+
"1. Publish Lambda version: aws lambda publish-version --function-name <WorkflowFunction>",
56+
"2. Invoke workflow: aws lambda invoke --function-name <WorkflowFunction>:<version> --invocation-type Event --payload '{...}' response.json",
57+
"3. List approvals: aws dynamodb scan --table-name <ApprovalsTable>",
58+
"4. Submit decision: aws lambda invoke --function-name <ApprovalApiFunction> --payload '{\"action\":\"decide\",...}' response.json"
59+
]
60+
},
61+
"cleanup": {
62+
"text": [
63+
"Delete the stack: sam delete"
64+
]
65+
},
66+
"authors": [
67+
{
68+
"name": "Mian Tariq",
69+
"bio": "Cloud Solutions Architect",
70+
"linkedin": "mian-tariq"
71+
}
72+
],
73+
"patternArch": {
74+
"icon1": {
75+
"name": "lambda",
76+
"label": "Lambda durable functions"
77+
},
78+
"icon2": {
79+
"name": "dynamodb",
80+
"label": "Amazon DynamoDB"
81+
},
82+
"icon3": {
83+
"name": "sns",
84+
"label": "Amazon SNS"
85+
}
86+
}
87+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM public.ecr.aws/lambda/python:3.13
2+
3+
# Copy requirements file
4+
COPY approval_api/requirements.txt ${LAMBDA_TASK_ROOT}/
5+
6+
# Install dependencies
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
# Copy function code
10+
COPY approval_api/app.py ${LAMBDA_TASK_ROOT}/
11+
COPY shared/*.py ${LAMBDA_TASK_ROOT}/
12+
13+
# Set the CMD to your handler
14+
CMD ["app.lambda_handler"]

0 commit comments

Comments
 (0)