Spis treści 14 sekcji
- Problem: jeden event, wiele reakcji
- Architektura: SNS → SQS → Lambda
- Krok 1: Utwórz SNS Topic
- Krok 2: Utwórz kolejki SQS
- Krok 3: Subskrypcje SNS → SQS
- Krok 4: Lambda functions
- Krok 5: Podłącz SQS → Lambda
- Krok 6: Publikuj event z aplikacji
- Message filtering - subskrypcje z filtrem
- Dead Letter Queue - gdy coś pójdzie nie tak
- Koszty systemu powiadomień
- SNS vs SQS vs EventBridge - kiedy co?
- FAQ - najczęstsze pytania o SNS + SQS
- Co dalej?
Problem: jeden event, wiele reakcji
Wyobraź sobie sklep internetowy. Klient składa zamówienie. Co musi się stać?
- Email potwierdzający do klienta
- SMS z numerem zamówienia
- Aktualizacja stanu magazynowego
- Powiadomienie Slack do zespołu obsługi
- Event do systemu analityki
- Aktualizacja dashboardu w czasie rzeczywistym
Bez event-driven architectury każda z tych akcji to osobne wywołanie w kodzie obsługi zamówienia. Monolith rośnie, jedno powolne wysyłanie emaila blokuje cały proces, a dodanie nowej reakcji wymaga modyfikacji kodu zamówień.
Rozwiązanie: publish-subscribe pattern z SNS + SQS + Lambda.
Architektura: SNS → SQS → Lambda

| Serwis | Rola | Analogia |
|---|---|---|
| SNS (Topic) | Rozgłoszenie eventu do wielu subskrybentów | Megafon na placu |
| SQS (Queue) | Buforowanie wiadomości, retry przy błędach | Skrzynka pocztowa |
| Lambda (Function) | Przetwarzanie każdej wiadomości | Pracownik przy biurku |
Przepływ
- Aplikacja publikuje event "zamówienie złożone" do SNS Topic
- SNS fan-out - rozsyła event do wszystkich subskrybentów (kolejek SQS)
- Każda kolejka SQS buforuje event niezależnie
- Lambda triggerowana z SQS przetwarza wiadomość
- Jeśli Lambda fail → wiadomość wraca do kolejki → retry
- Jeśli retry wyczerpane → Dead Letter Queue (DLQ) do analizy
order_id w DynamoDB i pomijaj duplikaty, żeby klient nie dostał dwóch emaili, a magazyn nie zszedł podwójnie.Krok 1: Utwórz SNS Topic
- Wejdź w SNS → Topics → Create topic
- Type: Standard (nie FIFO - potrzebujemy fan-out)
- Name:
order-events - Kliknij Create topic
aws sns create-topic --name order-events
# Zanotuj TopicArn z odpowiedzi:
# arn:aws:sns:eu-central-1:123456789:order-events
Krok 2: Utwórz kolejki SQS
Tworzymy osobną kolejkę dla każdego "konsumenta" eventu + Dead Letter Queue:
- Wejdź w SQS → Create queue
- Type: Standard
- Name:
order-email-queue - Visibility timeout: 60 seconds (musi być > Lambda timeout)
- Message retention: 4 days
- Dead-letter queue: włącz, utwórz nową
order-email-dlq, max receives: 3 - Kliknij Create queue
- Powtórz dla:
order-sms-queue,order-inventory-queue,order-analytics-queue,order-slack-queue
# Dead Letter Queue
aws sqs create-queue --queue-name order-email-dlq
# Główna kolejka z DLQ
aws sqs create-queue --queue-name order-email-queue \
--attributes '{
"VisibilityTimeout": "60",
"MessageRetentionPeriod": "345600",
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:eu-central-1:123456789:order-email-dlq\",\"maxReceiveCount\":\"3\"}"
}'
# Kolejka SMS
aws sqs create-queue --queue-name order-sms-queue \
--attributes '{
"VisibilityTimeout": "60",
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:eu-central-1:123456789:order-sms-dlq\",\"maxReceiveCount\":\"3\"}"
}'
# Kolejka inventory
aws sqs create-queue --queue-name order-inventory-queue \
--attributes '{
"VisibilityTimeout": "60",
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:eu-central-1:123456789:order-inventory-dlq\",\"maxReceiveCount\":\"3\"}"
}'
# Kolejka analytics
aws sqs create-queue --queue-name order-analytics-queue
# Kolejka Slack
aws sqs create-queue --queue-name order-slack-queue
Krok 3: Subskrypcje SNS → SQS
- Wejdź w SNS → Topics → order-events → Create subscription
- Protocol: Amazon SQS
- Endpoint: ARN kolejki
order-email-queue - Kliknij Create subscription
- Powtórz dla pozostałych kolejek
- Ważne: Na stronie SQS, każda kolejka musi mieć Access Policy pozwalającą SNS na wysyłanie wiadomości
# Subskrypcja email queue
aws sns subscribe \
--topic-arn arn:aws:sns:eu-central-1:123456789:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-central-1:123456789:order-email-queue
# Subskrypcja SMS queue
aws sns subscribe \
--topic-arn arn:aws:sns:eu-central-1:123456789:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-central-1:123456789:order-sms-queue
# Subskrypcja inventory queue
aws sns subscribe \
--topic-arn arn:aws:sns:eu-central-1:123456789:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-central-1:123456789:order-inventory-queue
# Subskrypcja analytics queue
aws sns subscribe \
--topic-arn arn:aws:sns:eu-central-1:123456789:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-central-1:123456789:order-analytics-queue
# Subskrypcja Slack queue
aws sns subscribe \
--topic-arn arn:aws:sns:eu-central-1:123456789:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-central-1:123456789:order-slack-queue
# Access policy na kolejce SQS (pozwól SNS wysyłać)
# Powtórz dla KAŻDEJ kolejki - bez tej policy SNS po cichu nie dostarczy do niej wiadomości
aws sqs set-queue-attributes \
--queue-url https://sqs.eu-central-1.amazonaws.com/123456789/order-email-queue \
--attributes '{
"Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":\"sqs:SendMessage\",\"Resource\":\"arn:aws:sqs:eu-central-1:123456789:order-email-queue\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"arn:aws:sns:eu-central-1:123456789:order-events\"}}}]}"
}'
Krok 4: Lambda functions
Lambda: wysyłka emaila
import json
import boto3
ses = boto3.client('ses', region_name='eu-central-1')
def handler(event, context):
for record in event['Records']:
# SQS body zawiera SNS message
sns_message = json.loads(record['body'])
order = json.loads(sns_message['Message'])
order_id = order['order_id']
email = order['customer_email']
total = order['total']
items = order['items']
items_html = "".join(
f"<li>{item['name']} x{item['qty']} - {item['price']} zł</li>"
for item in items
)
ses.send_email(
Source='[email protected]',
Destination={'ToAddresses': [email]},
Message={
'Subject': {'Data': f'Potwierdzenie zamówienia #{order_id}'},
'Body': {
'Html': {'Data': f"""
<h2>Dziękujemy za zamówienie!</h2>
<p>Numer: <strong>#{order_id}</strong></p>
<ul>{items_html}</ul>
<p>Suma: <strong>{total} zł</strong></p>
"""}
}
}
)
print(f"Email wysłany do {email} dla zamówienia #{order_id}")
Lambda: aktualizacja magazynu
import json
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Inventory')
def handler(event, context):
for record in event['Records']:
sns_message = json.loads(record['body'])
order = json.loads(sns_message['Message'])
for item in order['items']:
# Atomowe zmniejszenie stanu magazynowego
table.update_item(
Key={'product_id': item['product_id']},
UpdateExpression='SET stock = stock - :qty',
ConditionExpression='stock >= :qty',
ExpressionAttributeValues={
':qty': item['qty']
}
)
print(f"Magazyn zaktualizowany: {item['product_id']} (-{item['qty']})")
Lambda: Slack notification
import json
import os
import urllib3
# Webhook to sekret - trzymaj go w zmiennych środowiskowych Lambdy, nie w kodzie
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
def handler(event, context):
http = urllib3.PoolManager()
for record in event['Records']:
sns_message = json.loads(record['body'])
order = json.loads(sns_message['Message'])
msg = {
"text": f":shopping_cart: Nowe zamówienie #{order['order_id']}!",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Zamówienie #{order['order_id']}*\n"
f"Klient: {order['customer_email']}\n"
f"Suma: *{order['total']} zł*\n"
f"Produkty: {len(order['items'])}"
}
}
]
}
http.request('POST', SLACK_WEBHOOK_URL,
body=json.dumps(msg).encode('utf-8'),
headers={'Content-Type': 'application/json'}
)
print(f"Slack notification wysłany dla #{order['order_id']}")
Krok 5: Podłącz SQS → Lambda
# Event source mapping: SQS → Lambda
aws lambda create-event-source-mapping \
--function-name order-send-email \
--event-source-arn arn:aws:sqs:eu-central-1:123456789:order-email-queue \
--batch-size 10 \
--maximum-batching-window-in-seconds 5
aws lambda create-event-source-mapping \
--function-name order-update-inventory \
--event-source-arn arn:aws:sqs:eu-central-1:123456789:order-inventory-queue \
--batch-size 1
aws lambda create-event-source-mapping \
--function-name order-slack-notify \
--event-source-arn arn:aws:sqs:eu-central-1:123456789:order-slack-queue \
--batch-size 10
Krok 6: Publikuj event z aplikacji
import boto3
import json
from datetime import datetime
sns = boto3.client('sns', region_name='eu-central-1')
TOPIC_ARN = 'arn:aws:sns:eu-central-1:123456789:order-events'
def create_order(order_data):
"""Wywoływane z API/Lambda obsługującej zamówienia"""
# Zapisz zamówienie w bazie (DynamoDB/RDS)
order_id = save_to_database(order_data)
# Publikuj event - SNS rozsyła do wszystkich subskrybentów
sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps({
'order_id': order_id,
'customer_email': order_data['email'],
'customer_phone': order_data.get('phone'),
'items': order_data['items'],
'total': order_data['total'],
'timestamp': datetime.utcnow().isoformat()
}),
MessageAttributes={
'event_type': {
'DataType': 'String',
'StringValue': 'order.created'
},
'order_value': {
'DataType': 'Number',
'StringValue': str(order_data['total'])
}
}
)
return order_id
Message filtering - subskrypcje z filtrem
Nie każda kolejka musi dostawać każdy event. SNS pozwala filtrować wiadomości na podstawie atrybutów:
# Kolejka SMS dostaje eventy tylko dla zamówień > 500 zł
aws sns set-subscription-attributes \
--subscription-arn arn:aws:sns:eu-central-1:123456789:order-events:sub-sms \
--attribute-name FilterPolicy \
--attribute-value '{
"order_value": [{"numeric": [">=", 500]}]
}'
# Kolejka VIP dostaje eventy tylko typu "order.created" i "order.shipped"
aws sns set-subscription-attributes \
--subscription-arn arn:aws:sns:eu-central-1:123456789:order-events:sub-vip \
--attribute-name FilterPolicy \
--attribute-value '{
"event_type": ["order.created", "order.shipped"]
}'
Dead Letter Queue - gdy coś pójdzie nie tak
DLQ zbiera wiadomości, których Lambda nie mogła przetworzyć po 3 próbach (maxReceiveCount). To Twoja siatka bezpieczeństwa:
# Sprawdź ile wiadomości jest w DLQ
aws sqs get-queue-attributes \
--queue-url https://sqs.eu-central-1.amazonaws.com/123456789/order-email-dlq \
--attribute-names ApproximateNumberOfMessages
# Skrypt do przeglądania DLQ
import boto3, json
sqs = boto3.client('sqs', region_name='eu-central-1')
DLQ_URL = 'https://sqs.eu-central-1.amazonaws.com/123456789/order-email-dlq'
response = sqs.receive_message(
QueueUrl=DLQ_URL,
MaxNumberOfMessages=10,
MessageAttributeNames=['All']
)
for msg in response.get('Messages', []):
body = json.loads(msg['Body'])
print(f"Failed message: {json.dumps(body, indent=2)}")
# Po naprawieniu: sqs.delete_message(QueueUrl=DLQ_URL, ReceiptHandle=msg['ReceiptHandle'])
ApproximateNumberOfMessagesVisible w DLQ. Jeśli > 0, dostajesz powiadomienie. Zerowa tolerancja na wiadomości w DLQ.Koszty systemu powiadomień
| Serwis | Free Tier | Koszt (100K eventów/mc) |
|---|---|---|
| SNS (publish) | 1M requestów/mc | $0 (mieści się w free) |
| SNS → SQS (delivery) | Unlimited | $0 (SNS→SQS jest free) |
| SQS (standard) | 1M requestów/mc | ~$0.04 |
| Lambda (4 funkcje × 100K) | 1M invocations/mc | $0 (mieści się w free) |
| SES (emaile) | 3K emaili/mc przez pierwsze 12 mies. | ~$10 za 100K ($0.10/1K) |
| SUMA | ~$10-11/mc |
System obsługujący 100,000 zamówień miesięcznie za ~$10-11 (prawie wszystko to SES). Spróbuj to zrobić taniej z własnym serwerem RabbitMQ.
SNS vs SQS vs EventBridge - kiedy co?
| Serwis | Pattern | Kiedy używać |
|---|---|---|
| SNS | Pub/Sub (fan-out) | Jeden event → wiele reakcji jednocześnie |
| SQS | Queue (point-to-point) | Buforowanie, retry, rate limiting, decoupling |
| EventBridge | Event bus (routing) | Złożone reguły routingu, integracja z SaaS, scheduling |
| SNS + SQS | Fan-out + buffer | Niezawodny fan-out z retry per konsument (nasz use case) |
FAQ - najczęstsze pytania o SNS + SQS
Dlaczego SNS + SQS a nie samo SNS → Lambda?
Standard vs FIFO - kiedy wybrać FIFO?
Co się stanie jeśli Lambda jest wyłączona a wiadomości przychodzą?
Jak dodać nowego konsumenta (np. nowy typ powiadomienia)?
Co dalej?
- Utwórz SNS Topic i 2-3 kolejki SQS z DLQ.
- Napisz prostą Lambda przetwarzającą wiadomości i podepnij do SQS.
- Opublikuj testowy event i sprawdź czy przechodzi przez cały pipeline.
- Dodaj CloudWatch alarm na DLQ - zero tolerancji na utracone wiadomości.
- Rozbudowuj o Message Filtering i nowych konsumentów.
Event-driven architecture to fundament skalowalnych aplikacji na AWS. SNS + SQS + Lambda to trio, które obsługuje od kilku do milionów eventów dziennie bez zmian w architekturze. Jeśli chcesz zobaczyć te serwisy w kontekście pełnej aplikacji, sprawdź Use Case: SaaS za 200 zł lub architekturę webową na AWS.
