Chapter 13 — Capstone Project
Day 1 — Design and Deploy the Core Pipeline
1. Concept Primer
This capstone chains everything from Chapters 1–11 into one flow:
S3 upload → Lambda trigger → DynamoDB write → SNS notification → SQS consumer.
Today's goal is getting the chain deployed and firing end-to-end; Day 2 adds observability
and teardown.
2. Hands-on Exercise
Deploy the S3 bucket, DynamoDB table, Lambda function, SNS topic, and SQS queue, then wire the event chain together.
3. Exact Commands
floci start && eval $(floci env)
# Storage + table
aws s3 mb s3://capstone-uploads
aws dynamodb create-table \
--table-name CapstoneEvents \
--attribute-definitions AttributeName=EventId,AttributeType=S \
--key-schema AttributeName=EventId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# Messaging
TOPIC_ARN=$(aws sns create-topic --name capstone-events --query 'TopicArn' --output text)
QUEUE_URL=$(aws sqs create-queue --queue-name capstone-consumer --query 'QueueUrl' --output text)
QUEUE_ARN=$(aws sqs get-queue-attributes --queue-url $QUEUE_URL --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
aws sns subscribe --topic-arn $TOPIC_ARN --protocol sqs --notification-endpoint $QUEUE_ARN
# Lambda: writes to DynamoDB, publishes to SNS
mkdir -p capstone-fn && cd capstone-fn
cat > handler.py << 'EOF'
import json, boto3, uuid, os
dynamodb = boto3.client("dynamodb", endpoint_url=os.environ["AWS_ENDPOINT_URL"])
sns = boto3.client("sns", endpoint_url=os.environ["AWS_ENDPOINT_URL"])
def handler(event, context):
record = event["Records"][0]
key = record["s3"]["object"]["key"]
event_id = str(uuid.uuid4())
dynamodb.put_item(
TableName="CapstoneEvents",
Item={"EventId": {"S": event_id}, "Key": {"S": key}}
)
sns.publish(
TopicArn=os.environ["TOPIC_ARN"],
Message=json.dumps({"eventId": event_id, "key": key})
)
return {"statusCode": 200}
EOF
zip function.zip handler.py
cd ..
aws iam create-role --role-name capstone-role \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]
}'
aws lambda create-function \
--function-name capstone-fn \
--runtime python3.12 \
--handler handler.handler \
--role arn:aws:iam::000000000000:role/capstone-role \
--zip-file fileb://capstone-fn/function.zip \
--environment "Variables={AWS_ENDPOINT_URL=http://localhost:4566,TOPIC_ARN=$TOPIC_ARN}"
aws lambda add-permission \
--function-name capstone-fn \
--statement-id s3invoke \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::capstone-uploads
FN_ARN=$(aws lambda get-function --function-name capstone-fn --query 'Configuration.FunctionArn' --output text)
aws s3api put-bucket-notification-configuration \
--bucket capstone-uploads \
--notification-configuration "{
\"LambdaFunctionConfigurations\": [{
\"LambdaFunctionArn\": \"$FN_ARN\",
\"Events\": [\"s3:ObjectCreated:*\"]
}]
}"
# Fire the chain
echo "capstone test" > event.txt
aws s3 cp event.txt s3://capstone-uploads/event.txt
sleep 3
aws dynamodb scan --table-name CapstoneEvents
aws sqs receive-message --queue-url $QUEUE_URL
4. Gotchas
- The Lambda hardcodes
AWS_ENDPOINT_URLinto its own environment soboto3inside the function also talks to Floci — easy to forget, and the most common reason a "working" Lambda mysteriously can't reach DynamoDB/SNS when run as an event trigger vs. direct invoke. - Give the chain a few seconds (
sleep 3above) before checking DynamoDB/SQS — event delivery through S3 → Lambda → SNS → SQS is asynchronous even locally. - This reuses account ID
000000000000and role name conventions from earlier chapters — keep names unique per project if you're layering the capstone on top of leftover resources from previous chapters' exercises.
5. Self-Check
Trace the chain backward: if sqs receive-message returns nothing, which three prior
links in the chain would you check, in order, to isolate where it broke?
Day 2 — Observability, IaC-ify It, and Teardown
1. Concept Primer
A capstone isn't done until it's reproducible and disposable: wrap the whole thing in one CloudFormation/Terraform deploy, add basic logging visibility, and script the teardown so nothing lingers between runs.
2. Hands-on Exercise
Check Lambda logs from Day 1's run, add a CloudWatch alarm on invocation errors, then write and run a full teardown script.
3. Exact Commands
eval $(floci env)
# Confirm the function actually ran
aws logs tail /aws/lambda/capstone-fn --since 15m
# Basic error alarm (relies on the Lambda Errors metric AWS publishes automatically)
aws cloudwatch put-metric-alarm \
--alarm-name capstone-fn-errors \
--namespace AWS/Lambda \
--metric-name Errors \
--dimensions Name=FunctionName,Value=capstone-fn \
--statistic Sum \
--period 60 \
--evaluation-periods 1 \
--threshold 0 \
--comparison-operator GreaterThanThreshold
# Teardown script
cat > teardown.sh << 'EOF'
#!/bin/bash
set -e
eval $(floci env)
aws lambda delete-function --function-name capstone-fn || true
aws iam delete-role --role-name capstone-role || true
aws sns delete-topic --topic-arn $(aws sns list-topics --query "Topics[?contains(TopicArn,'capstone-events')].TopicArn" --output text) || true
QUEUE_URL=$(aws sqs get-queue-url --queue-name capstone-consumer --query 'QueueUrl' --output text 2>/dev/null || true)
[ -n "$QUEUE_URL" ] && aws sqs delete-queue --queue-url $QUEUE_URL || true
aws dynamodb delete-table --table-name CapstoneEvents || true
aws s3 rb s3://capstone-uploads --force || true
aws cloudwatch delete-alarms --alarm-names capstone-fn-errors || true
echo "Capstone teardown complete."
EOF
chmod +x teardown.sh
./teardown.sh
4. Gotchas
- The
AWS/Lambdanamespace'sErrorsmetric is one AWS publishes automatically per function — whether Floci populates it with the same fidelity as real AWS is worth double-checking; if the alarm never leavesINSUFFICIENT_DATA, that's likely why. || trueon every teardown line keeps the script running even if a resource was already deleted or never got created — useful for a script you'll re-run often while iterating.aws s3 rb ... --forcedeletes the bucket and everything in it — fine for a capstone sandbox, but never a pattern to reuse against anything with real data.
5. Self-Check
Why does the teardown script delete the Lambda function before the IAM role, and the SNS topic before the SQS queue — does the order actually matter here, and why?