Skip to content

Commit 49e0acf

Browse files
committed
New serverless pattern - Serverless Messaging Redrive- added improvements
1 parent 5048d72 commit 49e0acf

File tree

6 files changed

+270
-65
lines changed

6 files changed

+270
-65
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Serverless Message Processing Pattern
2+
3+
## Overview
4+
An adaptable pattern for message processing using AWS serverless services, featuring error handling and automatic recovery mechanisms.
5+
6+
## Core Components
7+
- API Gateway (message ingestion)
8+
- SQS Queues (main + DLQs)
9+
- Lambda Functions (processing + recovery)
10+
11+
## Basic Flow
12+
1. Messages enter through API Gateway
13+
2. Main queue receives messages
14+
3. Processor Lambda handles messages
15+
4. Failed messages route to DLQs
16+
5. Decision maker attempts an automated recovery
17+
18+
## Deployment
19+
# Build the SAM application
20+
```
21+
sam build
22+
```
23+
# Deploy the application
24+
```
25+
sam deploy --guided
26+
```
27+
28+
## Key Features
29+
- Automatic retry mechanism
30+
- Segregation of recoverable/fatal errors
31+
- Extensible processing logic
32+
33+
## API Reference
34+
# Send Message
35+
```
36+
37+
POST /message
38+
Content-Type: application/json
39+
```
40+
```
41+
{
42+
"messageType": "TYPE_A|TYPE_B|TYPE_C",
43+
"payload": {},
44+
"timestamp": "ISO8601_TIMESTAMP"
45+
}
46+
```
47+
48+
49+
## Adaptation Points
50+
- Message validation rules
51+
- Processing logic
52+
- Error handling strategies
53+
- Recovery mechanisms
54+
- Monitoring requirements
55+
- API Design
56+
57+
## Note
58+
This is a sample pattern. Adapt security, scaling, and processing logic according to your requirements.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"title": "Step Functions to Athena",
3+
"description": "Create a Step Functions workflow to query Amazon Athena.",
4+
"language": "Python",
5+
"level": "200",
6+
"framework": "SAM",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This sample project demonstrates how to use a serverless solution for processing and fixing malformed messages using SQS queues and Lambda functions",
11+
"The system automatically handles message validation, applies fixes where possible, and routes messages to appropriate queues based on their fixability.",
12+
"It has built-in error handling and detailed logging, it provides a robust framework for message processing that can be easily extended for specific business needs.",
13+
"This pattern uses AWS Lambda for processing, multiple SQS queues for message routing, and includes 2 dead-letter queue (DLQ) for messages requiring human intervention or for auto-remediation."
14+
]
15+
},
16+
"gitHub": {
17+
"template": {
18+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/serverless-messaging-processing",
19+
"templateURL": "serverless-patterns/serverless-messaging-processing",
20+
"projectFolder": "serverless-messaging-processing",
21+
"templateFile": "template.yaml"
22+
}
23+
},
24+
"resources": {
25+
"bullets": [
26+
{
27+
"text": "Amazon SQS Docs",
28+
"link": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html"
29+
},
30+
{
31+
"text": "Using dead-letter queues in Amazon SQS",
32+
"link": "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html"
33+
}
34+
]
35+
},
36+
"deploy": {
37+
"text": [
38+
"sam build",
39+
"sam deploy --guided"
40+
]
41+
},
42+
"testing": {
43+
"text": [
44+
"See the GitHub repo for detailed testing instructions."
45+
]
46+
},
47+
"cleanup": {
48+
"text": [
49+
"Delete the stack: <code>sam delete</code>."
50+
]
51+
},
52+
"authors": [
53+
{
54+
"name": "Ilias Ali",
55+
"image": "link-to-your-photo.jpg",
56+
"bio": "I am a Solutions Architect working at AWS based in the UK.",
57+
"linkedin": "ilias-ali-0849991a4"
58+
}
59+
]
60+
}

serverless-message-processing/functions/decision_maker/app.py

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import re
23
import boto3
34
import os
45
import logging
@@ -14,109 +15,133 @@
1415
MAIN_QUEUE_URL = os.environ['MAIN_QUEUE_URL']
1516
FATAL_DLQ_URL = os.environ['FATAL_DLQ_URL']
1617

17-
def can_fix_message(message):
18+
# Email validation pattern
19+
EMAIL_PATTERN = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
20+
21+
def fix_email(email):
1822
"""
19-
Determine if a message can be fixed automatically.
20-
21-
Extension points:
22-
1. Add validation for specific message formats
23-
2. Implement business-specific fix rules
24-
3. Add data transformation logic
25-
4. Implement retry strategies
26-
5. Add validation against external systems
23+
Attempt to fix common email format issues
24+
Can be amended to other scenarios e.g. Downstream issues
2725
"""
28-
try:
29-
# Basic message validation
30-
# Add your validation logic here
31-
return True
32-
except Exception as e:
33-
logger.error(f"Validation error: {str(e)}")
34-
return False
26+
# Remove multiple @ symbols, keep the last one
27+
if email.count('@') > 1:
28+
parts = email.split('@')
29+
email = f"{parts[0]}@{parts[-1]}"
30+
31+
# Remove spaces
32+
email = email.strip().replace(' ', '')
33+
34+
# Fix common typos in domain extensions
35+
common_fixes = {
36+
'.con': '.com',
37+
'.vom': '.com',
38+
'.comm': '.com',
39+
'.orgg': '.org',
40+
'.nett': '.net'
41+
}
42+
43+
for wrong, right in common_fixes.items():
44+
if email.endswith(wrong):
45+
email = email[:-len(wrong)] + right
46+
47+
return email
3548

36-
def fix_message(message):
49+
def can_fix_email(message):
3750
"""
38-
Apply fixes to the message.
39-
40-
Extension points:
41-
1. Add data normalization
42-
2. Implement field-specific fixes
43-
3. Add data enrichment
44-
4. Implement format conversion
45-
5. Add validation rules
51+
Check if the email in the message can be fixed
4652
"""
47-
try:
48-
fixed_message = message.copy()
49-
# Add your fix logic here
50-
fixed_message['wasFixed'] = True
51-
return fixed_message
52-
except Exception as e:
53-
logger.error(f"Fix error: {str(e)}")
54-
return None
53+
if 'email' not in message:
54+
return False
55+
56+
email = message['email']
57+
fixed_email = fix_email(email)
58+
59+
return bool(re.match(EMAIL_PATTERN, fixed_email))
60+
5561

5662
def lambda_handler(event, context):
5763
"""
58-
Process messages and route them based on fixability.
59-
64+
Processes messages from a DLQ that have already failed to be automatically processed,
65+
and attempts automated remediation and redelivery of the messages back to the main queue.
66+
If no suitable fixes can be applied, messages end up in a fatal DLQ where the typical
67+
approach of human intervention is required.
68+
6069
Flow:
6170
1. Attempt to fix message
6271
2. If fixable -> Main Queue
6372
3. If unfixable -> Fatal DLQ
6473
6574
Extension points:
66-
1. Add more sophisticated routing logic
75+
1. Add more sophisticated routing logic- including a delay queue
6776
2. Implement custom error handling
6877
3. Add message transformation
6978
4. Implement retry mechanisms
7079
5. Add monitoring and metrics
71-
"""
80+
81+
"""
7282
processed_count = 0
7383

7484
for record in event['Records']:
75-
message_id = 'unknown' # Initialize message_id with default value
76-
7785
try:
86+
# Parse the message body
7887
message = json.loads(record['body'])
79-
message_id = record.get('messageId', 'unknown')
88+
original_message_id = record.get('messageId', 'unknown')
8089

81-
logger.info(f"Processing message: {message_id}")
90+
logger.info(f"Processing failed message: {original_message_id}")
8291

83-
if can_fix_message(message):
84-
fixed_message = fix_message(message)
85-
if fixed_message:
86-
# Send to main queue
87-
sqs.send_message(
88-
QueueUrl=MAIN_QUEUE_URL,
89-
MessageBody=json.dumps(fixed_message)
90-
)
91-
logger.info(f"Fixed message sent to main queue: {message_id}")
92-
else:
93-
raise ValueError("Message fix failed")
92+
93+
94+
95+
# Option A: Try to fix malformed email
96+
97+
if can_fix_email(message) and not re.match(EMAIL_PATTERN, message['email']):
98+
fixed_email = fix_email(message['email'])
99+
logger.info(f"Fixed email from '{message['email']}' to '{fixed_email}'")
100+
101+
# Update the message with fixed email
102+
message['email'] = fixed_email
103+
message['emailWasFixed'] = True
104+
105+
# Send back to main queue
106+
sqs.send_message(
107+
QueueUrl=MAIN_QUEUE_URL,
108+
MessageBody=json.dumps(message)
109+
)
110+
111+
logger.info(f"Sent fixed message back to main queue: {original_message_id}")
112+
113+
# Option B: Cannot fix - send to fatal DLQ
94114
else:
115+
logger.warning(f"Message cannot be fixed, sending to fatal DLQ: {original_message_id}")
116+
117+
# Add failure reason if not present
118+
if 'failureReason' not in message:
119+
message['failureReason'] = 'Unrecoverable error - could not fix message'
120+
95121
# Send to fatal DLQ
96-
message['failureReason'] = 'Message cannot be automatically fixed'
97122
sqs.send_message(
98123
QueueUrl=FATAL_DLQ_URL,
99124
MessageBody=json.dumps(message)
100125
)
101-
logger.warning(f"Message sent to fatal DLQ: {message_id}")
102-
126+
103127
processed_count += 1
104128

105129
except Exception as e:
106-
logger.error(f"Error processing message {message_id}: {str(e)}")
130+
logger.error(f"Error processing message {original_message_id}: {str(e)}")
131+
# If we can't process the decision, send to fatal DLQ
107132
try:
108133
error_message = {
109134
'originalMessage': record['body'],
110-
'failureReason': str(e),
135+
'failureReason': f"Decision maker error: {str(e)}",
111136
'timestamp': context.invoked_function_arn
112137
}
113138
sqs.send_message(
114139
QueueUrl=FATAL_DLQ_URL,
115140
MessageBody=json.dumps(error_message)
116141
)
117-
logger.error(f"Error message sent to fatal DLQ: {message_id}")
142+
118143
except Exception as fatal_e:
119-
logger.critical(f"Fatal DLQ error: {str(fatal_e)}")
144+
logger.critical(f"Could not send to fatal DLQ: {str(fatal_e)}")
120145
raise
121146

122147
return {
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
boto3==1.26.137
2-
jsonschema==4.17.3
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
boto3==1.26.137
2-
jsonschema==4.17.3

0 commit comments

Comments
 (0)