Hands-On Lab: Building a Loosely Coupled Serverless Architecture
Design scalable and loosely coupled architectures
Prerequisites
Before starting this lab, ensure you have the following ready:
- AWS Account: An active AWS account with Administrator or PowerUser access.
- AWS CLI Installed: The AWS Command Line Interface installed on your local machine.
- AWS Credentials Configured: Run
aws configureto set up your access keys, secret keys, and default region (e.g.,us-east-1). - Basic Python Knowledge: Familiarity with basic Python syntax and JSON structures.
- Zip Utility: A command-line zip tool to package your Lambda function.
Learning Objectives
By completing this lab, you will be able to:
- Design loosely coupled architectures by introducing a message broker (Amazon SQS) between components.
- Deploy stateless compute workloads using AWS Lambda.
- Manage API creation by configuring an Amazon API Gateway to trigger serverless functions.
- Demonstrate event-driven concepts where asynchronous background processing can scale independently from frontend requests.
Architecture Overview
In this lab, you will build a serverless architecture where a client sends an HTTP request to API Gateway. API Gateway routes the request to a stateless Lambda function (Producer), which then formats the payload and places it into an Amazon SQS Queue (Buffer).
This pattern prevents traffic spikes from overwhelming backend processing systems, achieving true loose coupling.
Step-by-Step Instructions
Step 1: Create the Amazon SQS Queue
The first step to decoupling our application is creating the buffer—an Amazon Simple Queue Service (SQS) queue.
aws sqs create-queue --queue-name brainybee-lab-queue[!TIP] Save the
QueueUrlreturned in the output. You will need it to configure the Lambda function's environment variables in Step 3.
▶Console alternative
- Navigate to the Amazon SQS console.
- Click Create queue.
- Leave the type as Standard.
- Name the queue
brainybee-lab-queue. - Leave all other settings as default and click Create queue.
📸 Screenshot: The SQS creation success banner showing your new Queue URL.
Step 2: Create an IAM Role for the Lambda Function
AWS Lambda needs an execution role granting it permission to write logs to CloudWatch and send messages to your SQS queue.
First, create the trust policy document:
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": { "Service": "lambda.amazonaws.com" },
"Effect": "Allow"
}
]
}
EOFNext, create the role and attach the necessary policies:
# Create the role
aws iam create-role \
--role-name brainybee-lambda-role \
--assume-role-policy-document file://trust-policy.json
# Attach basic Lambda execution permissions (CloudWatch logs)
aws iam attach-role-policy \
--role-name brainybee-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Attach SQS permissions
aws iam attach-role-policy \
--role-name brainybee-lambda-role \
--policy-arn arn:aws:iam::aws:policy/AmazonSQSFullAccess▶Console alternative
- Navigate to IAM > Roles > Create role.
- Select AWS service and choose Lambda as the use case. Click Next.
- Search for and check the boxes for
AWSLambdaBasicExecutionRoleandAmazonSQSFullAccess. Click Next. - Name the role
brainybee-lambda-roleand click Create role.
Step 3: Deploy the Stateless Lambda Producer
Now we will write a stateless Python function that captures the API request and forwards it to the SQS queue.
Create the Python file:
cat > lambda_function.py <<EOF
import json
import boto3
import os
sqs = boto3.client('sqs')
QUEUE_URL = os.environ['QUEUE_URL']
def lambda_handler(event, context):
# Extract body from API Gateway event, or use a default message
body = event.get('body', 'Hello from the BrainyBee decoupled architecture!')
response = sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=body
)
return {
'statusCode': 200,
'body': json.dumps({
'message': 'Successfully buffered in SQS!',
'messageId': response['MessageId']
})
}
EOFZip the code and deploy the function (replace <YOUR_ACCOUNT_ID> and <YOUR_REGION> with your actual AWS details, or run the dynamic bash variables if using Linux/macOS):
# Zip the deployment package
zip function.zip lambda_function.py
# Dynamically fetch the Queue URL and Account ID for the deployment
QUEUE_URL=$(aws sqs get-queue-url --queue-name brainybee-lab-queue --query QueueUrl --output text)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Create the function
aws lambda create-function \
--function-name brainybee-lab-producer \
--runtime python3.9 \
--role arn:aws:iam::${ACCOUNT_ID}:role/brainybee-lambda-role \
--handler lambda_function.lambda_handler \
--zip-file fileb://function.zip \
--environment Variables="{QUEUE_URL=${QUEUE_URL}}"[!NOTE] If you get an
InvalidParameterValueExceptionregarding the role, wait 10 seconds and try again. IAM role creation is eventually consistent.
▶Console alternative
- Navigate to the AWS Lambda console and click Create function.
- Name it
brainybee-lab-producer, select Python 3.9 as the runtime. - Under Permissions, choose Use an existing role and select
brainybee-lambda-role. - Click Create function.
- Scroll down to Code source, paste the Python code from above, and click Deploy.
- Go to the Configuration tab > Environment variables > Edit.
- Add a variable with Key:
QUEUE_URLand Value:<your-sqs-queue-url>, then save.
Step 4: Configure API Gateway
To make our Lambda accessible over the web, we'll configure an HTTP API using Amazon API Gateway.
REGION=$(aws configure get region)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 1. Grant API Gateway permission to invoke your Lambda
aws lambda add-permission \
--function-name brainybee-lab-producer \
--statement-id apigateway-invoke \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com
# 2. Create the HTTP API mapped directly to the Lambda function
aws apigatewayv2 create-api \
--name brainybee-lab-api \
--protocol-type HTTP \
--target arn:aws:lambda:${REGION}:${ACCOUNT_ID}:function:brainybee-lab-producer[!TIP] The output will contain an
ApiEndpoint(e.g.,https://xxxxxx.execute-api.us-east-1.amazonaws.com). Copy this URL.
▶Console alternative
- Navigate to the API Gateway console.
- Click Build under HTTP API.
- Click Add integration, select Lambda, and choose
brainybee-lab-producer. - Name the API
brainybee-lab-apiand click Next. - Leave the route configuration as default (Method:
ANY, Resource path:/brainybee-lab-producer). Click Next. - Leave the stages as default (
$default) and click Next, then Create.
📸 Screenshot: The invoke URL found on the API Gateway overview page.
Checkpoints
Let's verify that your loosely coupled architecture is working correctly.
Checkpoint 1: Invoke the API
Use curl (or your browser) to send a request to your API Gateway endpoint. Replace the URL with your specific ApiEndpoint from Step 4.
curl -X POST https://<YOUR_API_ID>.execute-api.<REGION>.amazonaws.com/ \
-H "Content-Type: application/json" \
-d '"This is a test of a scalable, decoupled system!"'Expected Output:
{"message": "Successfully buffered in SQS!", "messageId": "..."}
Checkpoint 2: Verify the Message in the Buffer (SQS)
The API response was near-instant because Lambda didn't process the data; it simply dropped it in the queue. Now let's verify the message is safely stored in our SQS buffer.
QUEUE_URL=$(aws sqs get-queue-url --queue-name brainybee-lab-queue --query QueueUrl --output text)
aws sqs receive-message \
--queue-url ${QUEUE_URL} \
--max-number-of-messages 1Expected Output: A JSON structure displaying your message body ("This is a test of a scalable, decoupled system!") and metadata. Because this is a decoupled architecture, this message will wait in the queue securely until a backend consumer instance is ready to process it.
Teardown
[!WARNING] Remember to run the teardown commands to avoid ongoing charges in your AWS account. While this lab primarily uses the AWS Free Tier, cleaning up is a vital cloud engineering habit.
Execute the following commands to destroy all provisioned resources:
# 1. Delete API Gateway
API_ID=$(aws apigatewayv2 get-apis --query "Items[?Name=='brainybee-lab-api'].ApiId" --output text)
aws apigatewayv2 delete-api --api-id ${API_ID}
# 2. Delete Lambda Function
aws lambda delete-function --function-name brainybee-lab-producer
# 3. Delete SQS Queue
QUEUE_URL=$(aws sqs get-queue-url --queue-name brainybee-lab-queue --query QueueUrl --output text)
aws sqs delete-queue --queue-url ${QUEUE_URL}
# 4. Detach Policies and Delete IAM Role
aws iam detach-role-policy --role-name brainybee-lambda-role --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam detach-role-policy --role-name brainybee-lambda-role --policy-arn arn:aws:iam::aws:policy/AmazonSQSFullAccess
aws iam delete-role --role-name brainybee-lambda-role
# 5. Clean up local files
rm trust-policy.json lambda_function.py function.zipTroubleshooting
| Problem | Potential Cause | Solution |
|---|---|---|
InvalidParameterValueException: The role defined for the function cannot be assumed by Lambda. | Eventual consistency in AWS IAM. The role was created but hasn't propagated globally yet. | Wait 10-15 seconds and re-run the aws lambda create-function command. |
Internal Server Error when calling the API | The QUEUE_URL environment variable is missing or malformed in your Lambda configuration. | Check the Lambda console under Configuration -> Environment variables. Ensure the value is a valid HTTPS SQS endpoint. |
API Gateway returns {"message":"Forbidden"} | You are using the wrong HTTP method or hitting the wrong route path. | Check the route configured in API Gateway. If using the default CLI command above, hitting the root path (/) should work. Verify your ApiEndpoint. |
aws: command not found | The AWS CLI is not installed or not added to your system's PATH. | Follow the AWS documentation to install the AWS CLI for your specific operating system, then run aws configure. |