DEV Community

Cover image for Event-Driven Architecture on AWS Explained Simply (With SAM)

Event-Driven Architecture on AWS Explained Simply (With SAM)

Ever heard the term "event-driven architecture" and felt like it was written in a different language? You're not alone.
In this post, I'll break it all down using plain English, real analogies, and working code you can run entirely on your laptop using AWS SAM, no AWS account charges required.
By the end, you'll understand how EventBridge, SNS, SQS, and Lambda work together, and you'll have built a fully connected event-driven pipeline locally.

Think of it Like a Shopping Mall

Before any code, let me paint a picture.
You own a shopping mall. A customer places an order. Several things need to happen:

  • The warehouse needs to pack the item
  • The accounts team needs to record the payment
  • The delivery team needs to schedule a shipment

The old way? You'd call each team one by one. Slow, tightly coupled, and fragile. If one team is busy, everything stalls.
The new way? You post a message on a notice board. Every team that needs to act sees it automatically and responds in their own time.
That notice board is event-driven architecture. And AWS gives us four services to build it.

Prerequisites

Before you start, make sure you have the following installed on your Mac/Linux machine:

Tool Why You Need It
AWS CLI To configure your AWS credentials
AWS SAM CLI To build and run Lambda locally
Python 3.12 The runtime for our Lambda functions
Docker To simulate the Lambda execution environment

Quick check — run these in your terminal to confirm everything is ready:

aws --version
sam --version
python3 --version
docker --version
Enter fullscreen mode Exit fullscreen mode

No AWS deployment needed! We run everything locally using SAM and Docker, so no AWS account charges apply while following this guide.

Architecture

lambda,sqs,sns,eventbridge
The diagram shows the live event flow. The phases build it in reverse starting from Lambda and working back to EventBridge — so each layer is testable before adding the next

The 4 Services, Explained Simply

1. EventBridge — The Smart Postman

EventBridge is your event bus, the central place where things get published when something happens in your app.
You define rules that tell it where to route events:

If the event comes from myapp.orders and the type is OrderPlaced, send it to SNS.

It reads the label on every package and delivers it to exactly the right destination.

// An EventBridge event looks like this
{
  "source": "myapp.orders",
  "detail-type": "OrderPlaced",
  "detail": {
    "orderId": "ORD-789",
    "product": "Tablet",
    "amount": 749.99,
    "customer": "Emma Ulu"
  }
}
Enter fullscreen mode Exit fullscreen mode

2. SNS — The Group Chat

SNS (Simple Notification Service) is your megaphone. Once EventBridge sends an event to an SNS topic, SNS fans it out to all subscribers simultaneously.
You could have one subscriber or a hundred. They all get the message at the same time. This is the fan-out pattern.

One message in, many receivers out.

3. SQS — The Voicemail Box

SQS (Simple Queue Service) is a waiting line for messages. When SNS drops a message into an SQS queue, it sits there patiently until someone picks it up.
Why does this matter? Because if your Lambda function is busy or crashes, the message doesn't get lost. It just waits.

Like voicemail. If you're busy, the message waits. You won't miss it.

4. Lambda — The Worker

Lambda is your actual worker function, the code that does the real job. It picks up messages from SQS one by one and processes them: saving to a database, sending an email, charging a card, notifying another service. Anything you need.

The Full Flow

Customer places an order
        ↓
EventBridge checks its rules
        ↓
Rule matches → sends to SNS Topic
        ↓
SNS fans out → drops into SQS Queue
        ↓
Lambda picks up the message
        ↓
Order is processed ✅
Enter fullscreen mode Exit fullscreen mode

Clean. Decoupled. Scalable.

What is AWS SAM?
SAM (Serverless Application Model) is the tool that lets you define and test all of this locally. Instead of clicking around in the AWS console, you write a template.yaml that describes your infrastructure as code.

SAM runs everything in Docker containers on your machine. No deployment needed while learning.
Think of it as a flight simulator. You learn to fly before touching the real plane.

Think of it as a flight simulator. You learn to fly before touching the real plane.

Building It — Phase by Phase

I built this project in 4 phases, each adding one layer to the architecture.

Phase 1 — Lambda Foundation

First, a simple Lambda function that can receive and log any event.

hello_world/app.py

import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info(f"Received event: {json.dumps(event)}")
    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Hello from Event-Driven App!"})
    }
Enter fullscreen mode Exit fullscreen mode

Test it locally:

sam build
sam local invoke HelloWorldFunction --event events/event.json --skip-pull-image
Enter fullscreen mode Exit fullscreen mode

Phase 2 — Add SQS

Now we wire SQS to trigger Lambda whenever a message arrives in the queue.
template.yaml (key additions)

EventQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: event-queue

HelloWorldFunction:
  Type: AWS::Serverless::Function
  Properties:
    Events:
      SQSEvent:
        Type: SQS
        Properties:
          Queue: !GetAtt EventQueue.Arn
          BatchSize: 1
Enter fullscreen mode Exit fullscreen mode

Updated app.py to handle SQS Records:

def lambda_handler(event, context):
    for record in event.get("Records", []):
        body = json.loads(record["body"])
        logger.info(f"Processing message: {body}")
    return {"statusCode": 200, "body": "SQS messages processed!"}
Enter fullscreen mode Exit fullscreen mode

Test event (events/sqs_event.json):

{
  "Records": [{
    "body": "{\"orderId\": \"ORD-123\", \"product\": \"Laptop\", \"amount\": 999.99}",
    "eventSource": "aws:sqs"
  }]
}
Enter fullscreen mode Exit fullscreen mode
sam local invoke HelloWorldFunction --event events/sqs_event.json --skip-pull-image
Enter fullscreen mode Exit fullscreen mode

Output:
Processing message: {'orderId': 'ORD-123', 'product': 'Laptop', 'amount': 999.99}

✅ SQS → Lambda working!

Phase 3 — Add SNS Fan-out

Now SNS sits between your publisher and SQS, enabling fan-out to multiple queues if needed.
template.yaml additions:

EventTopic:
  Type: AWS::SNS::Topic

EventTopicSubscription:
  Type: AWS::SNS::Subscription
  Properties:
    TopicArn: !Ref EventTopic
    Protocol: sqs
    Endpoint: !GetAtt EventQueue.Arn
    RawMessageDelivery: true
Enter fullscreen mode Exit fullscreen mode

When SNS delivers to SQS, it wraps the message in an SNS envelope. Update app.py to unwrap it:

parsed_body = json.loads(record["body"])

if parsed_body.get("Type") == "Notification":
    # Unwrap the SNS envelope
    sns_message = json.loads(parsed_body["Message"])
    logger.info(f"Subject: {parsed_body.get('Subject')}")
    logger.info(f"Message: {sns_message}")
Enter fullscreen mode Exit fullscreen mode

✅ SNS → SQS → Lambda working!

Phase 4 — Add EventBridge

The final piece. EventBridge becomes the entry point for all events, routing them to SNS based on rules.
template.yamladditions:

EventBus:
  Type: AWS::Events::EventBus
  Properties:
    Name: event-driven-bus

OrderPlacedRule:
  Type: AWS::Events::Rule
  Properties:
    EventBusName: !Ref EventBus
    EventPattern:
      source:
        - "myapp.orders"
      detail-type:
        - "OrderPlaced"
    Targets:
      - Arn: !Ref EventTopic
        Id: SNSTarget
Enter fullscreen mode Exit fullscreen mode

Update app.py to extract the EventBridge payload from inside the SNS envelope:

if parsed_body.get("Type") == "Notification":
    sns_message = json.loads(parsed_body["Message"])

    source = sns_message.get("source")
    detail_type = sns_message.get("detail-type")
    detail = sns_message.get("detail", {})

    logger.info(f"Source: {source}")
    logger.info(f"Event Type: {detail_type}")
    logger.info(f"Detail: {detail}")

    if detail_type == "OrderPlaced":
        process_order(detail)

def process_order(detail):
    logger.info(f"Order ID: {detail.get('orderId')}")
    logger.info(f"Product: {detail.get('product')}")
    logger.info(f"Amount: ${detail.get('amount')}")
    logger.info(f"Customer: {detail.get('customer')}")
Enter fullscreen mode Exit fullscreen mode

Final test output:

EventBridge Source: myapp.orders
Event Type: OrderPlaced
Processing order: ORD-789
Product: Tablet
Amount: $749.99
Customer: Emma ulu
Enter fullscreen mode Exit fullscreen mode

✅ Full pipeline working — 100% locally!

final call

Why Not Just Call Lambda Directly?

Direct Call Event-Driven
Tight coupling Loosely coupled
One failure breaks others Services fail independently
Hard to add new features Just add a new SNS subscriber
Hard to scale Each service scales independently

The event-driven approach lets your teams and services operate independently. A new feature? Just subscribe to the event. No need to touch existing code.

What I Learned (Phase Summary)

Phase Service Added Pattern Learned
1 Lambda Local SAM testing, template.yaml basics
2 SQS Message queues, Records array
3 SNS Fan-out, envelope unwrapping
4 EventBridge Event buses, content-based routing

What's Next?

Now that you've got the foundation, here's where to go next:

  • Dead Letter Queue (DLQ) — never lose a failed message
  • Multiple Lambda consumers — fan-out to different Lambdas for different jobs
  • EventBridge Pipes — direct SQS→Lambda without SNS
  • Schema Registry — validate your event shapes
  • Deploy to AWS — sam deploy --guided when you're ready

Final Thoughts

Event-driven architecture sounds intimidating, but at its core, it's just a smarter way for services to talk to each other. You post an event, the right services react, and nothing is tightly coupled.
AWS SAM makes it approachable by letting you build and test everything locally before spending a single dollar on the cloud.
If you followed along and got it working, well done! You've just built a real-world architecture pattern used by companies at scale.
Drop a comment if you have questions or want me to go deeper on any phase. Happy building!

Built with: AWS SAM · EventBridge · SNS · SQS · Lambda · Python 3.12

Top comments (0)