Decoupling with Queues & Events
In a tightly coupled system, one component calls another directly and waits for an answer. If the callee is slow, overloaded, or down, the caller suffers too. Loose coupling breaks that direct link by putting a messaging layer in the middle, so a slow or failed component does not take its neighbours down with it. This page explains how to decouple components using SQS (Simple Queue Service), SNS (Simple Notification Service), and EventBridge, and the trade-offs you accept when you do.
What “decoupling” actually means
Decoupling means a producer (the component that creates work or events) and a consumer (the component that processes them) no longer talk to each other directly. Instead the producer drops a message into a buffer, and the consumer reads it on its own schedule. They do not need to be online at the same time, and they can scale independently.
The benefit is resilience and independent scaling. The cost is that you move from a simple synchronous request-response model to an asynchronous one, which brings eventual consistency (data becomes correct after a short delay, not instantly), the chance of duplicate messages, and ordering concerns. You should decouple where those trade-offs are worth it, not everywhere.
When NOT to decouple: If a user clicks a button and must see the result on the next screen (for example, “is this username available?”), use a plain synchronous call. Wrapping that in a queue just adds latency and complexity for no resilience gain.
SQS — a queue that buffers load
Amazon SQS (Simple Queue Service) is a fully managed message queue. A producer sends a message; one consumer receives it, processes it, and deletes it. The queue holds messages until a consumer is ready, which lets it absorb traffic spikes: if 10,000 orders arrive in a burst, they sit safely in the queue while your workers process them at a steady rate.
When to use this: Background jobs, order processing, video transcoding, any work that can happen “soon” rather than “right now,” and any time you want to smooth out spikes so downstream systems are not overwhelmed.
There are two queue types:
| Queue type | Ordering | Duplicates | Throughput | Use when |
|---|---|---|---|---|
| Standard | Best-effort | At-least-once (can repeat) | Nearly unlimited | High volume, order does not matter |
| FIFO | Strict, per message group | Exactly-once processing | 300 msg/s (3,000 batched) | Order matters (e.g. bank transactions) |
Create a queue and send a message
Console steps:
- Open the Amazon SQS console.
- Choose Create queue.
- Pick Standard or FIFO, name it (FIFO names must end in
.fifo), e.g.orders-queue. - Set Visibility timeout (how long a message is hidden after a consumer picks it up) to match your processing time, e.g. 30 seconds.
- Choose Create queue.
AWS CLI (v2):
aws sqs create-queue \
--queue-name orders-queue \
--attributes VisibilityTimeout=30
Output:
{
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/orders-queue"
}
aws sqs send-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/orders-queue \
--message-body '{"orderId":"A-1029","amount":42.50}'
Output:
{
"MD5OfMessageBody": "9f3c2e1b7a8d4f5e6c0b1a2d3e4f5a6b",
"MessageId": "5b8e0c1f-2a3d-4e5f-9a0b-1c2d3e4f5a6b"
}
SNS and EventBridge — fan out events
A queue delivers each message to a single consumer. Sometimes one event needs to reach many consumers. That is fan-out, and it is what SNS and EventBridge do.
- SNS (Simple Notification Service) is a pub/sub topic. A publisher sends to a topic; SNS pushes a copy to every subscriber (SQS queues, Lambda functions, HTTP endpoints, email, SMS). Use it for simple “broadcast this to everyone” patterns.
- EventBridge is an event bus with content-based routing. Producers put events on the bus; rules match events by their content (e.g.
detail-type = "OrderPlaced") and route them to targets. Use it when different consumers care about different events, or when you integrate with SaaS and AWS-service events.
| Service | Pattern | Routing | Best for |
|---|---|---|---|
| SQS | Point-to-point | None — one consumer | Buffering work, background jobs |
| SNS | Pub/sub fan-out | By subscription | Broadcast to many subscribers fast |
| EventBridge | Event bus | Rules on event content | Routing, SaaS/AWS integration, schemas |
A very common, robust pattern is SNS fan-out to SQS: SNS pushes each event to several SQS queues, and each consumer drains its own queue at its own pace. You get fan-out plus per-consumer buffering.
Create a topic and subscribe a queue
CLI:
aws sns create-topic --name order-events
Output:
{
"TopicArn": "arn:aws:sns:us-east-1:123456789012:order-events"
}
aws sns subscribe \
--topic-arn arn:aws:sns:us-east-1:123456789012:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:us-east-1:123456789012:orders-queue
Console steps (EventBridge rule):
- Open the Amazon EventBridge console, choose Rules, then Create rule.
- Name it
route-order-placed, leave the default event bus selected. - Under Event pattern, match
"detail-type": ["OrderPlaced"]. - Add a Target — your SQS queue or Lambda function.
- Choose Create.
Designing for the trade-offs
Because messages can be delivered more than once and arrive out of order, your consumers must be built defensively.
Make consumers idempotent. Idempotent means processing the same message twice has the same effect as processing it once. Store a processed-message ID (e.g. in DynamoDB) and skip anything you have already seen. FIFO queues help, but idempotency is the real safety net.
Always attach a dead-letter queue (DLQ). A DLQ is a separate queue that catches messages a consumer repeatedly fails to process, so one poison message does not block the queue forever. Configure it with a maxReceiveCount (how many delivery attempts before the message is moved aside).
{
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:123456789012:orders-dlq\",\"maxReceiveCount\":\"5\"}"
}
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/orders-queue \
--attributes file://redrive.json
Cost note: SQS and SNS bill per request — roughly $0.40 per million SQS requests and $0.50 per million SNS publishes (first 1M/month free). EventBridge custom events cost about $1.00 per million. For most workloads this is a few dollars a month; the bigger cost is usually the Lambda or EC2 compute that processes the messages, not the messaging layer itself.
Best Practices
- Decouple where buffering and independent scaling genuinely help; keep synchronous calls for anything that needs an immediate response.
- Build every consumer to be idempotent so duplicate deliveries are harmless.
- Attach a DLQ to every queue and alarm on its depth — a growing DLQ means something is failing silently.
- Use FIFO queues only when strict ordering is required; prefer Standard queues for throughput.
- Use SNS-to-SQS fan-out so each consumer gets its own buffered copy and can fail independently.
- Set the visibility timeout to a bit longer than your worst-case processing time to avoid a second consumer grabbing an in-flight message.
- Enable long polling (
ReceiveMessageWaitTimeSeconds=20) to cut empty receives and lower cost.