Apariencia
📙 Clase 9 — Arquitecturas Event Driven
Python para Backend · 2026-09-03 · Carpeta:
02-Ejercicios/Clase-09/PYT30JUL26/S09⬅️ Volver al índice de clases
🎯 Qué aprendí
- Qué es una arquitectura orientada a eventos (EDA) y en qué se diferencia de llamar directo a otro servicio
- Repaso de comunicación síncrona vs. asíncrona aplicado a
Orders → Inventory - Qué es un evento (hecho inmutable, en pasado) vs. un comando (orden, en imperativo) y la estructura recomendada de un evento (
event_id,event_type,occurred_at,data) - Amazon SNS y el patrón fan-out: un Topic distribuye un mensaje a todos sus suscriptores sin que el publisher los conozca
- Amazon SQS como buffer persistente y la diferencia clave SNS (distribución) vs. SQS (cola)
- El patrón SNS + SQS y el event source mapping de Lambda
- La arquitectura event-driven real del proyecto OrderFlow (Order Service → SNS → dos colas SQS → dos Lambdas consumidoras independientes)
- Código real con boto3: una Publisher Lambda que publica en SNS (
sns.publish) y una Consumer Lambda que consume de SQS (event["Records"]→json.loads)
🗺️ Índice
- 🎯 Qué aprendí
- 📖 PARTE TEÓRICA
- 💻 PARTE PRÁCTICA
- 🐍 7. Publisher y Consumer Lambda con boto3
- 🎯 8. Reto práctico: construye OrderFlow Event-Driven
- 🖥️ 9. Consola AWS: armar y probar OrderFlow de punta a punta
- 💰 Antes de empezar: alerta de presupuesto (zero-spend budget)
- 🔍 Buscar el servicio en la consola
- 📋 Crear el Topic
- ✅ Topic creado: OrderFlowOrderEvents
- 📥 Crear la primera cola SQS: OrderFlowInventoryQueue
- 📥 Crear la segunda cola SQS: OrderFlowNotificationQueue
- 🔗 Suscribir las colas al Topic
- ✅ Fan-out confirmado: el mensaje llegó a las dos colas
- 🚀 Desplegar las Lambdas en la consola
- 🚀 Segunda Lambda: orderflow-inventory-consumer
- 🔌 Conectar el trigger SQS
- 🚀 Tercera Lambda: orderflow-notification-consumer
- 🗄️ Verificar la tabla DynamoDB reutilizada
- 🔑 Permisos IAM: el publisher también los necesita
- 🧪 Crear el test event del publisher
- ✅ Publisher probado end-to-end: 202 con event_id y sns_message_id
- 🔧 Otro error de permisos: inventory_consumer no podía escribir en DynamoDB
- 🏋️ 10. EJERCICIOS CON SOLUCIÓN
- ❓ Preguntas y respuestas (autoevaluación)
- 📎 Apuntes relacionados
- ➡️ Siguiente
📖 PARTE TEÓRICA
Una arquitectura orientada a eventos (Event-Driven Architecture, EDA) es un estilo de diseño de software donde los componentes no se llaman directamente entre sí, sino que se comunican produciendo y reaccionando a eventos: hechos que ya ocurrieron en el sistema. En vez de que Orders le diga a Inventory "reserva este stock" y espere la respuesta (acoplado, síncrono), Orders anuncia "se creó el pedido ORD-1001" y cualquier servicio interesado (Inventory, Notifications, Billing…) reacciona por su cuenta, sin que Orders sepa quién lo escucha ni cuántos son.
📚 1. Definiciones clave
Arquitectura orientada a eventos
| Término | Qué es | Se profundiza en |
|---|---|---|
| Arquitectura orientada a eventos (EDA) | Estilo de diseño donde los servicios se comunican publicando y consumiendo eventos en vez de llamarse directamente — desacopla al emisor de quién (y cuántos) reaccionan. | intro de esta parte |
| Evento | Hecho inmutable que ya ocurrió en el sistema y queda registrado en el tiempo — no es una orden ni una petición. | sección 3 |
| Comando | Instrucción que le pide a alguien que haga algo ahora ("Haz esto ahora") — a diferencia del evento, no es un hecho consumado sino una petición de acción. | sección 3 |
event_id | Identificador único de cada evento; clave para garantizar idempotencia al consumirlo (detectar y descartar duplicados). | sección 3 |
occurred_at | Timestamp del evento; permite ordenar los eventos cronológicamente aunque lleguen desordenados al consumidor. | sección 3 |
| Idempotencia | Propiedad de una operación que produce el mismo resultado sin importar cuántas veces se procese la misma entrada — evita que un evento duplicado (p. ej. reintentado por la cola) cause efectos dobles. | sección 3 |
💡
Comunicación síncrona,asíncronayAcoplamiento temporalya están definidos en Clase 5, sección 5 — se retoman en la sección 2 sin volver a definirlos desde cero.
Mensajería en AWS (SNS / SQS)
| Término | Qué es | Se profundiza en |
|---|---|---|
| Amazon SNS (Simple Notification Service) | Servicio de AWS que implementa el patrón pub/sub: un mensaje publicado en un Topic se distribuye automáticamente a todos sus suscriptores. | sección 4 |
| Topic (SNS) | El canal de publicación dentro de SNS — los productores publican ahí y los suscriptores se enganchan a él, sin que el productor sepa quiénes son. | sección 4 |
| Fan-out | Patrón donde un único mensaje se reparte automáticamente a múltiples receptores a la vez, sin que el emisor conozca ni la cantidad ni la identidad de quién lo recibe. | sección 4 |
| Publisher | El componente que publica el mensaje en el Topic (en el ejemplo, Order Service). | sección 4 |
| Patrón SNS + SQS | Una cola SQS se suscribe a un Topic SNS y recibe automáticamente todo lo publicado en él — combina el fan-out de SNS con el buffer/durabilidad de una cola SQS. | sección 4 |
| Amazon SQS (Simple Queue Service) | Servicio de AWS que actúa como buffer persistente entre productor y consumidor — si el consumidor está caído o saturado, el mensaje espera en la cola en vez de perderse. | sección 5 |
| Event source mapping | Configuración de AWS Lambda que la suscribe directamente a una cola SQS (o a un stream) para consumir mensajes automáticamente, sin necesidad de hacer polling manual. | sección 5 |
| At-least-once delivery | Garantía de SQS Standard: si el consumidor falla antes de confirmar que procesó el mensaje, este reaparece en la cola y puede reintentarse — por diseño, un mismo mensaje puede entregarse más de una vez (nunca cero). | sección 5 |
| Standard vs. FIFO (SNS y SQS) | Standard: mejor throughput, orden best-effort, entrega at-least-once — el tipo que usa OrderFlow. FIFO (first-in, first-out): orden estrictamente preservada y entrega/procesamiento exactly-once, con throughput más limitado; en SNS, un Topic FIFO solo puede tener colas SQS como suscriptoras (no Lambda directo, ni HTTP, ni email). El tipo no se puede cambiar después de creado el Topic/la cola. | sección 9 |
MessageAttributes (SNS) | Metadata adicional que viaja junto al mensaje publicado (fuera del Message en sí) — p. ej. el event_type. Permite que un suscriptor filtre qué mensajes recibir (Subscription filter policy) sin tener que abrir y parsear el Message completo. | sección 7 |
batchItemFailures / partial batch response | Cuando Lambda procesa varios mensajes SQS en una sola invocación y solo algunos fallan, devolver {"batchItemFailures": [{"itemIdentifier": messageId}, ...]} le dice a SQS que reintente solo esos, no el batch completo. Requiere ReportBatchItemFailures habilitado en el event source mapping. | sección 7 |
🔁 2. Repaso: comunicación síncrona vs. asíncrona
💡 Este contraste ya se documentó a fondo en Clase 5, sección 5 con el ejemplo
orders → users/products/notifications(gRPC/Kafka). La Clase 9 lo retoma como punto de partida antes de entrar a Amazon SQS/SNS, con un ejemplo propio:Orders → Inventory.
🗺️ Diagrama: síncrono vs. asíncrono (ejemplo Orders → Inventory)

Síncrono: Orders realiza una llamada HTTP directa a Inventory y espera la respuesta. Si Inventory tarda, Orders también tarda. Si Inventory cae, Orders devuelve un error inmediato.
Asíncrono: Orders publica un evento en una cola. Puede continuar sin esperar. Inventory procesa el mensaje cuando está disponible, a su propio ritmo, sin bloquear al productor.
| Dimensión | Síncrono | Asíncrono |
|---|---|---|
| Espera de respuesta | Sí, siempre | No necesariamente |
| Acoplamiento temporal | Alto | Bajo |
| Mecanismo | HTTP directo | Mensajes / eventos |
| Resultado | Inmediato | Procesamiento posterior |
⚠️ ¿Asíncrono significa automáticamente "más rápido"? No. Significa que los componentes no tienen que ejecutarse al mismo tiempo. La latencia total puede incluso ser mayor, pero la disponibilidad y el desacoplamiento mejoran notablemente.
📨 3. ¿Qué es un evento?
Un evento describe algo que ya ocurrió en el sistema. No es una orden ni una petición — es un hecho inmutable registrado en el tiempo (ver glosario, sección 1).
🗺️ Diagrama: estructura de un evento y comando vs. evento

Estructura recomendada de un evento:
json
{
"event_id": "9bf2...",
"event_type": "OrderCreated",
"occurred_at": "2026-09-02T20:00:00Z",
"data": {
"order_id": "ORD-1001",
"product_id": "PROD-001",
"quantity": 2
}
}| Campo | Para qué sirve |
|---|---|
event_id | Identifica el evento de forma única — garantiza idempotencia (sección 1). |
event_type | Nombre del hecho ocurrido, en pasado (OrderCreated, no CreateOrder). |
occurred_at | Permite ordenar los eventos cronológicamente. |
data | El "payload": los datos concretos del hecho (qué pedido, qué producto, cuánto). |
🆚 Comando vs. Evento
| Comando | Evento | |
|---|---|---|
| Ejemplo | ReserveStock | OrderCreated |
| Intención | "Haz esto ahora" | "Esto ya ocurrió" |
| Quién lo dispara | Alguien que necesita que algo pase | El sistema, al registrar un hecho consumado |
| Se puede rechazar | Sí (puede fallar la reserva) | No — ya ocurrió, no se "rechaza" un hecho |
🧪 Tip de entrevista: el nombre de un comando va en imperativo (
ReserveStock,SendEmail); el nombre de un evento va en pasado (StockReserved,EmailSent). Ver el nombre alcanza para saber si algo es una orden o un hecho consumado.
Ejemplos de eventos: OrderCreated, PaymentApproved, StockReserved, UserRegistered.
📢 4. Amazon SNS: uno publica, muchos reciben
SNS implementa el patrón fan-out: un único mensaje publicado en un Topic se distribuye automáticamente a todos sus suscriptores. Orders no necesita conocer quién escucha.
📸 Captura: fan-out con Amazon SNS

| Paso | Qué pasa |
|---|---|
| 1. Publisher | Order Service publica OrderCreated en el Topic OrderFlowOrderEvents — un único punto de publicación. |
| 2. Fan-out automático | SNS distribuye el mensaje a cada suscriptor registrado: Inventory Queue, Notification Queue, Analytics, etc. |
| 3. Extensible sin cambios | Si mañana se añade Analytics Service, Orders no se modifica — solo se agrega un nuevo suscriptor al Topic. |
💡 Una cola SQS puede suscribirse a un Topic SNS y recibir automáticamente todos los mensajes publicados en él. Este es el patrón SNS + SQS que se usa en OrderFlow (ver glosario, sección 1).
🧪 Tip de entrevista: ¿por qué no publicar directo a varias colas SQS en vez de pasar por un Topic SNS? Porque con SNS,
Orderssolo conoce un destino (el Topic) y agregar/quitar suscriptores no le toca el código — sin SNS, cada nuevo consumidor obligaría a modificar quién publica.
🗺️ Diagrama de arquitectura: patrón fan-out (Order → SNS → SQS)
📥 5. Amazon SQS: la cola que desacopla
SQS actúa como buffer persistente entre el productor y el consumidor. Si Inventory Lambda está temporalmente detenida, los mensajes permanecen en la cola esperando — no se pierden.
📸 Captura: Amazon SQS y la diferencia con SNS

🆚 SNS vs. SQS: diferencia clave
| SNS → Distribución | SQS → Cola | |
|---|---|---|
| Qué hace | Entrega a múltiples suscriptores en tiempo real. | Almacena mensajes hasta que un consumidor los procese. |
| ¿Guarda mensajes? | No — si no hay suscriptor conectado en ese momento, no hay buffer. | Sí — es un buffer persistente, ideal para resiliencia. |
| Rol en el flujo | El que anuncia (fan-out). | El que espera y entrega cuando el consumidor puede. |
El flujo completo (SNS + SQS + Lambda): SNS publica → SQS almacena → Lambda procesa. Si Inventory Lambda está caída o saturada, el mensaje no se pierde — queda en la cola SQS hasta que Lambda vuelve a estar disponible para procesarlo.
💡 Lambda puede configurarse para consumir mensajes de SQS directamente mediante un event source mapping (ver glosario, sección 1), sin necesidad de polling manual — AWS se encarga de invocar la función cuando hay mensajes nuevos.
🧪 Tip de entrevista: ¿por qué no usar solo SNS y saltarse SQS? Porque SNS no almacena — si el suscriptor está caído en el instante de la publicación, ese mensaje se pierde. SQS agrega el buffer que falta: por eso el patrón SNS + SQS (sección 4) combina "avisar a todos" (SNS) con "no perder nada" (SQS).
⚠️ ¿Qué pasa cuando algo falla? (at-least-once)
Las colas SQS Standard ofrecen entrega at-least-once: si el consumidor falla antes de confirmar el procesamiento, el mensaje reaparece en la cola y puede ser reintentado. Esto es una feature, no un bug — pero exige diseño cuidadoso.
📸 Captura: qué pasa cuando algo falla

El flujo de un reintento: Lambda falla → Mensaje en SQS → Consumidor reintenta → Procesado exitoso. El mensaje no se pierde ni se descarta solo porque la primera invocación falló — SQS lo vuelve a ofrecer hasta que alguien lo confirme.
⚠️ El problema sin idempotencia: si procesamos
OrderCreated(quantity=2) dos veces —stock -2y luegostock -2de nuevo — reservamos el doble del stock necesario. Es exactamente el riesgo del Ejercicio 19: at-least-once es justamente lo que hace que ese riesgo sea real y no solo teórico — SQS puede entregar el mismo mensaje más de una vez por diseño.
🏗️ 6. Arquitectura completa de OrderFlow
Con SNS (sección 4) y SQS (sección 5) ya definidos, así se ve la arquitectura event-driven real que usa el proyecto OrderFlow — cada consumidor tiene su propia cola independiente: su ritmo de procesamiento, su gestión de fallos, sus reintentos y su responsabilidad de negocio propia.
📸 Captura: arquitectura completa de OrderFlow (swimlane)

| Carril | Componente | Qué hace |
|---|---|---|
| Servicio de Pedidos | Order Service | Publica el evento OrderCreated en el Topic SNS OrderFlowOrderEvents. |
| Cola de Mensajes | OrderFlowOrderEvents (Topic SNS) | Hace fan-out a dos colas SQS: OrderFlowInventoryQueue y OrderFlowNotificationQueue. |
| Consumidores Lambda | Inventory Consumer Lambda | Se alimenta de OrderFlowInventoryQueue (event source mapping) y escribe en DynamoDB. |
| Destinos | Notification Consumer Lambda | Se alimenta de OrderFlowNotificationQueue y registra logs en CloudWatch. |
💡 Este es el mismo patrón SNS + SQS de la sección 4 y el mismo
event source mappingde la sección 5, aplicados con los nombres reales del proyecto: la sección 4 usabaInventory Queue/Notification Queuecomo ejemplo genérico — acá son literalmenteOrderFlowInventoryQueueyOrderFlowNotificationQueue.
⚠️ Cada consumidor tiene su propia cola: si
Inventory Consumer Lambdafalla o se satura, sus reintentos no afectan en nada aNotification Consumer Lambda— es el desacoplamiento de la sección 2 llevado a la práctica, un fallo en un consumidor no bloquea a los demás.
💻 PARTE PRÁCTICA
🐍 7. Publisher y Consumer Lambda con boto3
📸 Captura: código de Publisher (SNS) y Consumer (SQS)
![Slide con dos bloques de código Python: a la izquierda "Publisher Lambda — publicar en SNS" (importa boto3, arma un dict event con event_id/event_type/data y lo publica con sns.publish(TopicArn=TOPIC_ARN, Message=json.dumps(event)), con nota de que el publisher necesita el permiso IAM sns:Publish sobre el Topic); a la derecha "Consumer Lambda — consumir de SQS" (lambda_handler itera event["Records"], hace json.loads(record["body"]) y llama a process_message(message)), con dos tarjetas de responsabilidades — Inventory Consumer extrae product_id y quantity para reservar stock en DynamoDB, Notification Consumer extrae order_id y user_id para enviar notificación al cliente — y un callout: el productor no conoce la implementación de sus consumidores, esto es desacoplamiento real](/clase-09-publisher-consumer-lambda-codigo.png)
Publisher Lambda — publica en SNS:
python
import json, os
from uuid import uuid4
import boto3
TOPIC_ARN = os.environ["TOPIC_ARN"]
sns = boto3.client("sns")
event = {
"event_id": str(uuid4()),
"event_type": "OrderCreated",
"data": {
"order_id": "ORD-1001",
"product_id": "PROD-001",
"quantity": 2
}
}
sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps(event)
)📝 Esta versión del evento omite
occurred_at(a diferencia de la estructura completa de la sección 3) — es una simplificación del ejemplo de código, no un cambio en la convención: en un evento real de OrderFlow conviene mantener los 4 campos.
⚠️ El publisher necesita el permiso IAM
sns:Publishsobre el Topic correspondiente (mínimo privilegio, como en Clase 8).
Consumer Lambda — consume de SQS:
python
def lambda_handler(event, context):
for record in event["Records"]:
message = json.loads(
record["body"]
)
process_message(message)event["Records"] es la lista de mensajes que SQS entrega a Lambda en una sola invocación (puede traer varios a la vez); cada record["body"] es el string JSON que sns.publish mandó como Message — por eso el consumer hace json.loads para recuperar el mismo dict que armó el publisher.
| Consumidor | process_message extrae | Hace |
|---|---|---|
Inventory Consumer | product_id, quantity | Reserva stock en DynamoDB |
Notification Consumer | order_id, user_id | Envía notificación al cliente |
💡 El productor no conoce la implementación de sus consumidores.
Order Serviceno sabe queInventory Consumerusa DynamoDB ni queNotification Consumerenvía notificaciones — solo publica el evento. Esto es el desacoplamiento real de la sección 2 llevado a código.
💻 Código real verificado — proyecto S09
El código de arriba es la versión simplificada del slide. Esta es la versión completa y verificada del proyecto real (02-Ejercicios/Clase-09/PYT30JUL26/S09), con las 3 Lambdas, las políticas IAM y el evento de prueba.
README.md del proyecto — arquitectura en ASCII:
Order Publisher Lambda
|
v
SNS OrderFlowOrderEvents
/ \
v v
Inventory SQS Notification SQS
| |
v v
Inventory Lambda Notification Lambda
|
v
DynamoDB OrderFlowInventory
Usar Raw Message Delivery en las suscripciones SNS -> SQS.
La tabla OrderFlowInventory se reutiliza desde la sesión 8.💡 Confirma dos cosas ya vistas:
Raw Message Deliveryes obligatorio (sección 9, no opcional), y la tabla DynamoDBOrderFlowInventoryde Clase 8 se reutiliza — no se crea una tabla nueva para esta clase.
events/order_created_input.json — el input "de negocio" que recibe el Publisher (sin el sobre de evento todavía):
json
{
"order_id": "ORD-1001",
"user_id": 10,
"product_id": "PROD-001",
"quantity": 2,
"unit_price": 3200
}publisher/lambda_function.py:
python
import json
import logging
import os
from datetime import datetime, timezone
from uuid import uuid4
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
TOPIC_ARN = os.environ["TOPIC_ARN"]
sns = boto3.client("sns")
def lambda_handler(event, context):
required = ["order_id", "user_id", "product_id", "quantity", "unit_price"]
missing = [f for f in required if f not in event]
if missing:
return {"statusCode": 400, "body": json.dumps({"missing": missing})}
domain_event = {
"event_id": str(uuid4()),
"event_type": "OrderCreated",
"occurred_at": datetime.now(timezone.utc).isoformat(),
"data": {
"order_id": event["order_id"],
"user_id": event["user_id"],
"product_id": event["product_id"],
"quantity": event["quantity"],
"unit_price": event["unit_price"],
},
}
result = sns.publish(
TopicArn=TOPIC_ARN,
Message=json.dumps(domain_event),
MessageAttributes={
"event_type": {
"DataType": "String",
"StringValue": "OrderCreated"
}
},
)
logger.info("Publicado event_id=%s", domain_event["event_id"])
return {
"statusCode": 202,
"body": json.dumps({
"message": "Evento publicado",
"event_id": domain_event["event_id"],
"sns_message_id": result["MessageId"]
}),
}A diferencia del slide, esta versión sí arma el evento completo (los 4 campos de la sección 3), valida que el input tenga los 5 campos requeridos antes de publicar (400 si falta alguno), y agrega MessageAttributes con el event_type — eso es lo que permite, más adelante, un Subscription filter policy (visto vacío en la captura de la sección 9) para que una cola se suscriba solo a ciertos tipos de evento sin tener que abrir el Message completo.
inventory_consumer/lambda_function.py:
python
import json
import logging
import os
from decimal import Decimal
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
TABLE_NAME = os.environ["TABLE_NAME"]
table = boto3.resource("dynamodb").Table(TABLE_NAME)
def process_message(message):
if message.get("event_type") != "OrderCreated":
return
data = message["data"]
product_id = str(data["product_id"])
quantity = int(data["quantity"])
result = table.update_item(
Key={"product_id": product_id},
UpdateExpression=(
"SET available_stock = available_stock - :q, "
"reserved_stock = reserved_stock + :q"
),
ConditionExpression=(
"attribute_exists(product_id) AND available_stock >= :q"
),
ExpressionAttributeValues={":q": Decimal(quantity)},
ReturnValues="ALL_NEW",
)
logger.info(
"Reserva aplicada order_id=%s product_id=%s stock=%s",
data["order_id"],
product_id,
result["Attributes"]["available_stock"],
)
def lambda_handler(event, context):
failures = []
for record in event.get("Records", []):
try:
message = json.loads(record["body"])
process_message(message)
except Exception:
logger.exception("Error procesando message_id=%s", record.get("messageId"))
failures.append({"itemIdentifier": record["messageId"]})
return {"batchItemFailures": failures}💡 El
ConditionExpression(attribute_exists+available_stock >= :q) es exactamente el patrón de operaciones atómicas de Clase 8 — evita reservar más stock del disponible aunque lleguen varios pedidos casi al mismo tiempo.
⚠️
batchItemFailures— reintento parcial del batch. SQS puede entregarle a Lambda varios mensajes en una sola invocación (event["Records"]trae más de uno). Si uno falla y los demás no, devolver{"batchItemFailures": [...]}con elmessageIdde solo el que falló le dice a SQS "reintentá nada más ese" — sin esto, un solo mensaje con error haría reintentar el batch completo, reprocesando de nuevo los que sí habían salido bien. Requiere que la cola SQS ↔ Lambda tenga habilitadoReportBatchItemFailuresen su event source mapping (sección 1).
notification_consumer/lambda_function.py:
python
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
failures = []
for record in event.get("Records", []):
try:
message = json.loads(record["body"])
if message.get("event_type") == "OrderCreated":
data = message["data"]
logger.info(
"NOTIFICACION pedido=%s usuario=%s producto=%s cantidad=%s",
data["order_id"],
data["user_id"],
data["product_id"],
data["quantity"],
)
except Exception:
logger.exception("Error procesando message_id=%s", record.get("messageId"))
failures.append({"itemIdentifier": record["messageId"]})
return {"batchItemFailures": failures}IAM de mínimo privilegio (mismo principio que Clase 8):
iam/publisher-policy.json:
json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["sns:Publish"],
"Resource": "ARN_DEL_TOPIC_ORDERFLOWORDEREVENTS"
}]
}iam/inventory-policy.json:
json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:UpdateItem"],
"Resource": "ARN_DE_LA_TABLA_ORDERFLOWINVENTORY"
}]
}| Lambda | Permiso mínimo | Sobre qué recurso puntual |
|---|---|---|
publisher | sns:Publish | Solo el Topic OrderFlowOrderEvents |
inventory_consumer | dynamodb:GetItem, dynamodb:UpdateItem | Solo la tabla OrderFlowInventory |
🧪 Probar el flujo con "Publish message" en la consola
Antes de invocar el Publisher Lambda, se puede probar el Topic directo desde la consola de SNS, publicando a mano un mensaje con la forma exacta del domain_event que arma el código:


💡 Publicar el
domain_eventcompleto a mano (en vez de solo elorder_created_input.jsoncrudo) prueba los consumidores de punta a punta —inventory_consumerynotification_consumeresperanmessage["event_type"]ymessage["data"], no los campos sueltos del input original. Es una forma de probar SNS → SQS → Lambda sin depender de que el Publisher Lambda ya esté desplegado.
🎯 8. Reto práctico: construye OrderFlow Event-Driven
📸 Captura: reto práctico y el salto de esta sesión

Evidencias que debe demostrar cada equipo:
- Publicar
OrderCreateddesdeOrder Service. InventoryQueueyNotificationQueuereciben el evento.Inventory Consumeractualiza DynamoDB.Notification Consumerregistra log en CloudWatch.- Detener un consumer y ver el mensaje en espera en SQS.
- Reactivar el consumer y comprobar el procesamiento.
💡 Los pasos 5 y 6 son el ejercicio práctico del at-least-once de la sección 5: detener el consumer a propósito y ver que el mensaje no se pierde — queda esperando en la cola hasta reactivarlo. Es la demostración en vivo de por qué SQS es un buffer persistente.
🆚 El salto de esta sesión
| Sesión 8 | Sesión 9 | |
|---|---|---|
| Flujo | HTTP → Lambda | EVENTO → SNS / SQS → Lambda |
| Acoplamiento | Síncrono y acoplado | Asíncrono y desacoplado |
💡 "Este patrón es la base de sistemas resilientes, escalables y extensibles en producción" — es el mismo salto que ya se documentó en la sección 2 (síncrono vs. asíncrono) y en Clase 8, ahora aplicado de punta a punta en OrderFlow.
🖥️ 9. Consola AWS: armar y probar OrderFlow de punta a punta
Recorrido real en la consola de AWS para construir toda la infraestructura del reto práctico (sección 8): el Topic SNS, las dos colas SQS, las 3 Lambdas con sus triggers y permisos, y las pruebas end-to-end de publicación y fan-out.
💰 Antes de empezar: alerta de presupuesto (zero-spend budget)

Igual que la recomendación de Clase 8 (alerta de presupuesto antes de empezar un laboratorio en AWS), acá se arma con la plantilla Zero spend budget: notifica por mail apenas el gasto supera $0.01 — la forma más estricta de detectar cualquier cosa que no debería estar cobrando.
🔍 Buscar el servicio en la consola

📋 Crear el Topic

Al crear el Topic, la consola pide elegir el Type — no se puede cambiar después:


| SNS FIFO | SNS Standard | |
|---|---|---|
| Orden | Estrictamente preservado | Best-effort |
| Entrega | Exactly-once | At-least-once |
| Suscriptores permitidos | Solo colas SQS | SQS, Lambda, Data Firehose, HTTP/S, SMS, email, apps móviles |
⚠️ Para OrderFlow se elige Standard, no FIFO — porque
Notification Consumernecesita que el Topic pueda tener a Lambda (vía SQS) entre sus suscriptores con la flexibilidad de at-least-once ya vista en la sección 5. Un Topic FIFO restringiría de entrada las opciones de suscripción.
✅ Topic creado: OrderFlowOrderEvents

| Campo | Valor |
|---|---|
| Name | OrderFlowOrderEvents |
| ARN | arn:aws:sns:us-east-2:540659180627:OrderFlowOrderEvents |
| Type | Standard |
| Subscriptions | 0 (todavía sin colas suscritas) |
📥 Crear la primera cola SQS: OrderFlowInventoryQueue

SQS tiene la misma elección de tipo que SNS, con la misma consecuencia (You can't change the queue type after you create a queue):


| Campo | Valor |
|---|---|
| Name | OrderFlowInventoryQueue |
| Type | Standard |
| ARN | arn:aws:sqs:us-east-2:540659180627:OrderFlowInventoryQueue |
| Encryption | Amazon SQS key (SSE-SQS) — cifrado en reposo por defecto |
💡 La pestaña SNS subscriptions de la cola es justo donde se conecta esta cola al Topic
OrderFlowOrderEvents— el patrón SNS + SQS de la sección 4 armado a mano en la consola.
📥 Crear la segunda cola SQS: OrderFlowNotificationQueue

| Configuración | Valor |
|---|---|
| Visibility timeout | 30 segundos |
| Delivery delay | 0 segundos |
| Message retention period | 4 días |
| Maximum message size | 1024 KiB |
📝 Corrección de la captura: el nombre tipeado en la consola fue
OrderFlowNotificacionQueue(con "Notificacion" en español) — noOrderFlowNotificationQueue, el nombre que usan el diagrama real de la sección 6 y el resto de esta clase. Se documenta tal cual quedó creada en la consola (fuente fiel); si en la práctica real de tu equipo el nombre debe coincidir exactamente con el del diagrama, conviene borrar la cola y volver a crearla con el nombre en inglés antes de suscribirla al Topic.

🔗 Suscribir las colas al Topic

Con las dos colas ya creadas (OrderFlowInventoryQueue y OrderFlowNotificacionQueue), el siguiente paso es volver al Topic OrderFlowOrderEvents y usar Create subscription para suscribir cada cola con protocolo SQS — completando el fan-out de la sección 4 con infraestructura real.





⚠️
Enable raw message deliveryno es un detalle menor — conecta directo con el código de la sección 7. Sin marcarlo, SQS recibe el mensaje envuelto en un sobre JSON de SNS (con metadata comoTopicArn,MessageId, y el contenido real adentro de un campo"Message"). Con el checkbox marcado,record["body"]en el Consumer Lambda es directamente el JSON del evento ({"event_id": ..., "event_type": ...}) — que es justo lo que esperajson.loads(record["body"])en el código ya visto. Sin esta opción, esejson.loadsfuncionaría pero traería el sobre de SNS, no el evento — habría que hacer unjson.loadsextra sobre el campo"Message".
Ambas suscripciones, confirmadas:
💡 Ninguna de las dos suscripciones necesitó el botón Confirm subscription a mano — a diferencia de un endpoint de email o HTTP (que exige abrir un link para confirmar), una suscripción SQS dentro de la misma cuenta AWS se autoconfirma.
Como la primera cola (OrderFlowInventoryQueue) se suscribió antes de activar raw message delivery en la segunda, su suscripción quedó sin esa opción — se edita después para dejar las dos consistentes:

✅ Fan-out confirmado: el mensaje llegó a las dos colas
Con las suscripciones listas, se publica un mensaje de prueba desde el Topic (la misma prueba de la sección 7) y se verifica que ambas colas lo reciban — sin que el publisher sepa nada de ninguna de las dos:


Un solo sns.publish → un mensaje disponible en cada una de las dos colas. Es el fan-out de la sección 4 funcionando con infraestructura real: OrderFlowInventoryQueue y OrderFlowNotificacionQueue recibieron la misma copia del mensaje de forma independiente — cada una lo procesará (o no) a su propio ritmo, tal como describe el Ejercicio 13.
🚀 Desplegar las Lambdas en la consola
Con el Topic, las dos colas y sus suscripciones ya probadas, el siguiente paso es crear en la consola las 3 funciones Lambda cuyo código ya está verificado en la sección 7 (publisher, inventory_consumer, notification_consumer).


| Campo | Valor |
|---|---|
| Método | Author from scratch |
| Function name | orderflow-order-publisher |
| Runtime | Python 3.12 |
| Permissions | Rol de ejecución por defecto (logs a CloudWatch) — se reemplaza después por la política iam/publisher-policy.json de mínimo privilegio (sección 7) |
💡 El nombre
orderflow-order-publishersigue la misma convenciónorderflow-*que ya usó Clase 8 (orderflow-inventory) — fácil de identificar entre todos los recursos de la cuenta que pertenecen a este proyecto.
Con la función orderflow-order-publisher creada, faltan dos cosas antes de que pueda publicar: el código y la variable de entorno que ese código espera.

TOPIC_ARN se copia directo del Topic en la consola de SNS (visto en la sección "Topic creado") — es exactamente lo que lee os.environ["TOPIC_ARN"] en la línea 11 del código real:
![VS Code con el proyecto S09 abierto en el Explorer (events/, iam/, inventory_consumer/, notification_consumer/, publisher/) y publisher/lambda_function.py abierto, mostrando el import de boto3, TOPIC_ARN = os.environ["TOPIC_ARN"], la validación de campos requeridos y el armado del domain_event — el mismo código ya documentado en la sección 7](/clase-09-vscode-publisher-lambda-function.png)
💡 El código de
lambda_function.pyen VS Code es idéntico al ya documentado en la sección 7 — acá solo falta pegarlo en el editor inline de la consola (o subirlo como .zip) y guardar (Deploy).
![Editor inline de código de la Lambda orderflow-order-publisher con el banner verde "Successfully updated the function orderflow-order-publisher", mostrando el cuerpo de lambda_handler con el sns.publish(TopicArn=TOPIC_ARN, Message=json.dumps(domain_event), MessageAttributes={...}) y el return con statusCode 202; abajo, los botones Deploy, Test y la sección "TEST EVENTS [NONE SELECTED]" con "Create new test event"](/clase-09-consola-aws-lambda-publisher-deploy-exitoso.png)
orderflow-order-publisher desplegada con éxito. El código coincide con el ya verificado en la sección 7 — usa TopicArn=TOPIC_ARN (la variable de entorno recién configurada) y publica con MessageAttributes para habilitar filtros de suscripción a futuro. El siguiente paso natural es Create new test event con el order_created_input.json de la sección 7, para invocarla y confirmar que publica correctamente en el Topic.
🚀 Segunda Lambda: orderflow-inventory-consumer


| Campo | Valor |
|---|---|
| Function name | orderflow-inventory-consumer |
| Runtime | Python 3.12 |
| Trigger | Ninguno todavía — falta Add trigger apuntando a OrderFlowInventoryQueue |
| Environment variables | Ninguna todavía — el código de la sección 7 espera TABLE_NAME (la tabla OrderFlowInventory reutilizada de Clase 8) |
💡 A diferencia del publisher (que se dispara manualmente o por API), esta Lambda necesita un trigger — el event source mapping de la sección 1 que la conecta a
OrderFlowInventoryQueuepara que AWS la invoque sola cuando lleguen mensajes.
![Editor inline de código de orderflow-inventory-consumer con el banner verde "Successfully updated the function orderflow-inventory-consumer", mostrando TABLE_NAME = os.environ["TABLE_NAME"], table = boto3.resource("dynamodb").Table(TABLE_NAME), y el inicio de process_message con el chequeo message.get("event_type") != "OrderCreated" y el table.update_item con UpdateExpression, ConditionExpression y ExpressionAttributeValues](/clase-09-consola-aws-lambda-inventory-consumer-deploy-exitoso.png)
orderflow-inventory-consumer desplegada con éxito — código idéntico al verificado en la sección 7, incluido el ConditionExpression de reserva atómica de stock. Todavía falta conectarle el trigger SQS para que se invoque sola.
🔌 Conectar el trigger SQS
Con el código ya desplegado, falta lo que hace que la Lambda se invoque sola cuando llegan mensajes: el trigger SQS (el event source mapping de la sección 1 hecho clic por clic).


💡 Las etiquetas de la propia consola (
event-source-mapping,polling,queue) confirman el término exacto ya definido en el glosario — no es una simplificación del curso, es el nombre real que usa AWS.

Antes de confirmar, un vistazo a la pestaña Permissions de la Lambda muestra que todavía solo tiene permiso de escribir logs en CloudWatch — la policy de DynamoDB (iam/inventory-policy.json, sección 7) sigue pendiente de adjuntar:

Trigger confirmado:
⚠️ El trigger queda en estado
Creatingun momento antes de pasar aEnabled— es normal, AWS tarda unos segundos en activar el poller interno que vigila la cola.
🚀 Tercera Lambda: orderflow-notification-consumer

| Campo | Valor |
|---|---|
| Function name | orderflow-notification-consumer |
| Runtime | Python 3.12 |
Sigue el mismo patrón que orderflow-inventory-consumer (sección anterior): crear → pegar el código de notification_consumer/lambda_function.py (sección 7) → agregar el trigger SQS apuntando a OrderFlowNotificacionQueue. A diferencia de inventory_consumer, esta Lambda no necesita una policy IAM extra — solo lee el mensaje y escribe en CloudWatch Logs (el permiso que Lambda ya trae por defecto), no toca DynamoDB ni ningún otro servicio.
Trigger confirmado:
Con esto, las tres Lambdas (publisher, inventory_consumer, notification_consumer) están creadas y las dos consumidoras ya tienen su trigger SQS conectado — el mismo patrón repetido dos veces, cada una a su propia cola independiente (sección 6).
🗄️ Verificar la tabla DynamoDB reutilizada
Antes de probar el flujo completo, conviene revisar el estado real de la tabla OrderFlowInventory (reutilizada de Clase 8) para el producto que va a reservar el evento de prueba:

| Atributo | Valor antes de la prueba |
|---|---|
product_id (partition key) | PROD-001 |
available_stock | 20 |
reserved_stock | 0 |
product_name | Laptop empresarial |
💡 Mismo esquema documentado en Clase 8 — nada nuevo que crear acá, solo confirmar el punto de partida. Con el
order_created_input.jsonde prueba (product_id: "PROD-001",quantity: 2), el resultado esperado después de queinventory_consumerprocese el evento esavailable_stock: 18yreserved_stock: 2— el mismoConditionExpressionde reserva atómica de la sección 7 aplicado con datos reales.
🔑 Permisos IAM: el publisher también los necesita
Al invocar orderflow-order-publisher, sns.publish() falla si el rol de ejecución de la Lambda no tiene permiso — el mensaje de error es explícito: requiere permisos para publicar. Se soluciona adjuntando una policy al rol de la Lambda, igual que se hizo con el rol de inventory_consumer (sección 7).

⚠️ Ojo con el mínimo privilegio. Las opciones que aparecen acá son policies administradas por AWS —
AmazonSNSFullAccess, por ejemplo, da permiso sobre todos los Topics de la cuenta, no soloOrderFlowOrderEvents. Esto contradice el principio de mínimo privilegio ya aplicado en Clase 8 y en la propiaiam/publisher-policy.jsondel proyecto (sección 7), que solo permitesns:Publishsobre un Topic puntual. Para production, conviene crear/adjuntar una policy propia (comopublisher-policy.json) en vez de una managed policy tan amplia — usarAmazonSNSFullAccessacá es válido para explorar rápido en clase, pero no es el patrón a repetir en el proyecto real.
✅ Resuelto con mínimo privilegio, no con la managed policy. En vez de adjuntar AmazonSNSFullAccess, se crea una policy propia con Create policy en el editor JSON:
![Wizard "Create policy" de IAM, paso "Specify permissions", con el editor JSON mostrando exactamente la policy: Version 2012-10-17, Statement con Effect Allow, Action ["sns:Publish"], y Resource restringido al ARN puntual arn:aws:sns:us-east-2:540659180627:OrderFlowOrderEvents](/clase-09-iam-create-policy-publisher-json.png)
Esta policy es exactamente iam/publisher-policy.json (sección 7), ya con el placeholder ARN_DEL_TOPIC_ORDERFLOWORDEREVENTS reemplazado por el ARN real — sns:Publish sobre un solo Topic, no sobre todos. El callout anterior queda confirmado: se optó por el camino de mínimo privilegio.
⚠️ Al crearla, la consola rechazó el primer intento con
Failed to create policy— no por el JSON, sino por el nombre de la policy (Policy namede IAM solo acepta alfanuméricos y+=,.@_-). Caso real documentado en 06-Errores: IAM policyName inválido. En ese momento la policy quedó creada comoorderflow-order-publisher-role— más adelante en esta misma sección aparece con el nombreOrderPublishEvents, ver la nota 📝 correspondiente.
🧪 Crear el test event del publisher
Con el permiso ya resuelto, toca invocar orderflow-order-publisher de verdad — se crea un test event en vez de un curl/API real, igual que en Clase 8:

| Campo | Valor |
|---|---|
| Invocation type | Synchronous — espera la respuesta de la Lambda, hasta 15 minutos de timeout |
| Event name | TestPublisher |
| Event sharing | Private — solo visible para quien lo creó |
| Event JSON | Placeholder Hello World todavía — falta reemplazarlo por el order_created_input.json real (sección 7) |
💡 El
lambda_handlerdel publisher valida contraevent["order_id"],event["user_id"], etc. — el placeholder{"key1": "value1", ...}haría fallar la validación de campos requeridos (400, sección 7) apenas se ejecute. El siguiente paso es pegar el JSON real ahí antes de darle Test.
✅ Publisher probado end-to-end: 202 con event_id y sns_message_id
Con el order_created_input.json real pegado en el test event, Test ejecuta la Lambda de punta a punta — permiso IAM incluido:

json
{
"statusCode": 202,
"body": "{\"message\": \"Evento publicado\", \"event_id\": \"121ab3e7-9d78-47c0-be2d-a09128a0de05\", \"sns_message_id\": \"680180a5-9c56-525a-84ae-e3b1b1cf5bf2\"}"
}Se cierra el círculo de la sección 7: el return del código real (statusCode: 202, message, event_id, sns_message_id) aparece tal cual en la respuesta — la policy IAM (recién resuelta) le permitió a sns.publish() completar, y result["MessageId"] de SNS es el sns_message_id de la respuesta. Este event_id es justo lo que inventory_consumer y notification_consumer recibirán en su message["event_id"] al procesar el mensaje desde sus colas.
🔧 Otro error de permisos: inventory_consumer no podía escribir en DynamoDB
Al probar inventory_consumer, apareció el mismo tipo de problema que con el publisher: el rol de este último sigue con su policy inline de sns:Publish adjunta y funcionando:

📝 Nota sobre el nombre de la policy: acá aparece como
OrderPublishEvents, distinto deorderflow-order-publisher-role(el nombre con el que se creó más arriba en esta sección, tras el error depolicyNameinválido). El contenido es el mismo (sns:PublishsobreOrderFlowOrderEvents) — lo más probable es que se haya borrado y recreado con un nombre más descriptivo entre una captura y la otra, algo que no quedó registrado en pantalla. Se documenta la discrepancia en vez de asumir cuál nombre es "el correcto", ya que ambas capturas son reales.
El profesor encontró un error equivalente en los permisos de orderflow-inventory-consumer — su rol (orderflow-inventory-consumer-role-ky9ddi27) todavía no tenía adjunta la policy de DynamoDB (iam/inventory-policy.json, sección 7: dynamodb:GetItem/dynamodb:UpdateItem sobre la tabla OrderFlowInventory), y se corrigió del mismo modo que el publisher — creando/adjuntando la policy inline correspondiente al rol.
💡 Mismo patrón de diagnóstico que el error de IAM de Clase 8: el rol recién creado por Lambda solo trae permiso de CloudWatch Logs por defecto — cualquier otro servicio (SNS, DynamoDB) necesita su policy adjunta a mano, sección por sección, Lambda por Lambda.
(continúa: configurar TABLE_NAME en inventory_consumer, y confirmar que el stock de PROD-001 bajó a 18 tras procesar el evento — pendiente, se completa con lo que siga dictando el profe)
🏋️ 10. EJERCICIOS CON SOLUCIÓN
💡 Los 20 ejercicios trabajan diseño y razonamiento sobre EDA/SNS/SQS (clasificar, nombrar, escribir el JSON de un evento, diseñar una arquitectura) — apoyate en el código real de la sección 7 como referencia de cómo se ve esto implementado.
Ejercicio 1 — Comando o evento
Clasificá cada uno de estos nombres como Comando o Evento: ChargeCard, CardCharged, CancelOrder, OrderCancelled, SendInvoice, InvoiceSent.
🎯 Qué deberías lograr: 3 identificados como Comando y 3 como Evento, sin consultar la solución.
💡 ¿Sabías que…? — imperativo vs. pasado
El nombre de un comando es una instrucción, por eso va en imperativo (como si le hablaras a alguien: "hacé esto"). El nombre de un evento describe un hecho ya consumado, por eso va en pasado. Ver Clase 9, sección 3.
Comando: PublishArticle ("Hacé esto ahora")
Evento: ArticlePublished ("Esto ya ocurrió")Ver solución
| Nombre | Tipo | Por qué |
|---|---|---|
ChargeCard | Comando | Imperativo — "cobrá la tarjeta ahora" |
CardCharged | Evento | Pasado — "la tarjeta ya fue cobrada" |
CancelOrder | Comando | Imperativo — "cancelá el pedido ahora" |
OrderCancelled | Evento | Pasado — "el pedido ya fue cancelado" |
SendInvoice | Comando | Imperativo — "enviá la factura ahora" |
InvoiceSent | Evento | Pasado — "la factura ya fue enviada" |
Ejercicio 2 — De comando a evento
Para estos 4 comandos, escribí el nombre del evento que se publicaría si el comando se ejecuta con éxito: UpdateInventory, RegisterUser, ApprovePayment, ShipOrder.
🎯 Qué deberías lograr: 4 nombres de evento en pasado, gramaticalmente correctos en inglés (convención de la clase).
💡 ¿Sabías que…? — un comando puede fallar, un evento no
Un comando puede rechazarse (no hay stock, la tarjeta no tiene fondos). Un evento ya ocurrió, así que no se "rechaza" — a lo sumo se compensa con otro evento distinto. Ejemplo de referencia: ReserveSeat (comando) → si tiene éxito, SeatReserved (evento); si falla, no hay evento de éxito, hay un motivo de rechazo que se devuelve al llamador (no un evento).
Ver solución
| Comando | Evento resultante |
|---|---|
UpdateInventory | InventoryUpdated |
RegisterUser | UserRegistered |
ApprovePayment | PaymentApproved |
ShipOrder | OrderShipped |
Ejercicio 3 — Escribir un evento JSON
Escribí el JSON completo (con los 4 campos de la estructura recomendada) para un evento PaymentApproved de un pago de $49.90 del pedido ORD-2050.
🎯 Qué deberías lograr: un JSON válido con event_id, event_type, occurred_at y data — y data debe incluir al menos order_id y amount.
💡 ¿Sabías que…? — la estructura no cambia, solo el contenido
Los 4 campos (event_id, event_type, occurred_at, data) son siempre los mismos — lo único que cambia entre eventos es el event_type y qué va adentro de data. Ver sección 3.
json
{
"event_id": "a1c9...",
"event_type": "UserRegistered",
"occurred_at": "2026-09-03T14:00:00Z",
"data": { "user_id": "USR-500", "email": "ana@example.com" }
}Ver solución
json
{
"event_id": "9bf2-...",
"event_type": "PaymentApproved",
"occurred_at": "2026-09-03T20:15:00Z",
"data": {
"order_id": "ORD-2050",
"amount": 49.90,
"currency": "USD"
}
}Ejercicio 4 — Idempotencia en la práctica
OrderFlowInventoryQueue reintenta la entrega de un mensaje StockReserved (mismo event_id) porque Inventory Consumer Lambda tardó en confirmar la primera vez. Explicá en 2-3 líneas cómo debería comportarse un consumidor idempotente frente a este mensaje duplicado.
🎯 Qué deberías lograr: tu explicación debe mencionar que el consumidor guarda o chequea el event_id ya procesado, y que NO vuelve a aplicar el efecto (descontar stock) una segunda vez.
💡 ¿Sabías que…? — el "efecto" es lo que hay que proteger
Idempotencia no significa "ignorar el mensaje repetido" — significa que aplicarlo dos veces produce el mismo resultado que aplicarlo una vez. Ejemplo de referencia: un consumidor de EmailSent no reenvía el correo si ya tiene registrado ese event_id; guarda los event_id ya procesados (en una tabla, un set, un caché) y antes de actuar, pregunta "¿ya procesé este?".
Ver solución
Antes de descontar stock, Inventory Consumer Lambda debería verificar si el event_id del mensaje ya fue procesado (por ejemplo, consultando una tabla de "eventos procesados" en DynamoDB). Si ya está, descarta el mensaje sin volver a descontar stock — solo confirma la recepción a SQS. Si no está, procesa el evento y guarda el event_id como procesado. Así, recibir el mismo StockReserved una o diez veces deja el stock exactamente igual.
Ejercicio 5 — Ordenar eventos por occurred_at
Ordená cronológicamente estos 4 eventos por su occurred_at: OrderCreated (2026-09-03T20:10:00Z), PaymentApproved (2026-09-03T20:09:30Z), StockReserved (2026-09-03T20:10:15Z), OrderShipped (2026-09-04T08:00:00Z).
🎯 Qué deberías lograr: la secuencia correcta de 4 nombres, de más antiguo a más reciente.
💡 ¿Sabías que…? — por qué no alcanza con el orden de llegada
En sistemas distribuidos, los mensajes pueden llegar desordenados al consumidor (por reintentos, particiones, redes). occurred_at es la única fuente confiable del orden real en que ocurrieron los hechos — no el orden en que el consumidor los recibe. Ver glosario, sección 1.
Ver solución
PaymentApproved—20:09:30OrderCreated—20:10:00StockReserved—20:10:15OrderShipped—08:00:00del día siguiente
Ejercicio 6 — Síncrono o asíncrono
Para estas 4 operaciones de OrderFlow, decidí si conviene una llamada síncrona o un evento asíncrono, justificando con el criterio de la sección 2:
Order Servicevalida que el stock alcance antes de confirmar el pedido.Order Serviceavisa que el pedido fue enviado, para que se le mande un email.Payment Serviceverifica el saldo de una tarjeta antes de autorizar el cobro.Analytics Serviceregistra un evento de auditoría de cada pedido creado.
🎯 Qué deberías lograr: 2 síncronas y 2 asíncronas, cada una con una frase de justificación.
💡 ¿Sabías que…? — la pregunta clave
¿El que dispara la acción necesita la respuesta ya para poder seguir? Si sí, síncrono. Si solo necesita que "eventualmente" pase, asíncrono. Ejemplo de referencia: "reservar un asiento antes de vender el ticket" es síncrono (necesito saber si hay asiento antes de cobrar); "enviar la confirmación por WhatsApp" es asíncrono (la venta no depende de que el WhatsApp se entregue ya). Ver también Clase 5, Ejercicio 7.
Ver solución
- Síncrono —
Order Servicenecesita la respuesta de stock ya para decidir si confirma o rechaza el pedido; no puede seguir sin saberlo. - Asíncrono — nadie necesita esperar el email para que el pedido quede marcado como enviado; es el patrón
OrderShipped → Notification Queuede la sección 6. - Síncrono —
Payment Servicenecesita saber si hay fondos antes de decidir si autoriza el cobro. - Asíncrono — la auditoría no bloquea nada del flujo de negocio; puede procesarse cuando
Analytics Servicepueda.
Ejercicio 7 — Diseñar un fan-out nuevo
El evento UserRegistered debe llegar a 3 servicios: Welcome Email, Loyalty Points y Analytics. Diseñá la solución con SNS: nombre del Topic y qué colas SQS se suscriben (seguí la convención OrderFlow<Algo>Queue de la sección 6).
🎯 Qué deberías lograr: 1 Topic y 3 nombres de cola, cada una mapeada a su servicio consumidor.
💡 ¿Sabías que…? — el Topic no sabe cuántos suscriptores tiene
Agregar un cuarto suscriptor (por ejemplo Fraud Check) más adelante no requiere tocar quién publica — solo se crea una cola nueva y se suscribe al Topic. Ver sección 4, "Extensible sin cambios". Ejemplo de referencia: un Topic OrderFlowPaymentEvents con una sola cola OrderFlowInvoiceQueue hoy, al que mañana se le suscribe OrderFlowFraudQueue sin tocar Payment Service.
Ver solución
- Topic:
OrderFlowUserEvents OrderFlowWelcomeEmailQueue→ consumeWelcome Email ServiceOrderFlowLoyaltyQueue→ consumeLoyalty Points ServiceOrderFlowAnalyticsQueue→ consumeAnalytics Service
User Service publica UserRegistered una única vez en el Topic; SNS hace fan-out a las 3 colas.
Ejercicio 8 — El riesgo de SNS sin SQS
Explicá por qué publicar UserRegistered directo en SNS, sin colas SQS de por medio, puede hacer que Loyalty Points Service nunca reciba el mensaje si está caído en el momento de la publicación.
🎯 Qué deberías lograr: tu explicación debe decir explícitamente que SNS no almacena mensajes.
💡 ¿Sabías que…? — SNS entrega, no guarda
SNS distribuye en el momento; si el suscriptor no está disponible para recibir esa entrega, el mensaje se pierde — no queda ningún registro para reintentar más tarde. Ver sección 5, tabla SNS vs. SQS.
Ver solución
SNS es un servicio de distribución en tiempo real, no de almacenamiento: cuando publica un mensaje, intenta entregarlo a cada suscriptor en ese instante y no lo guarda en ningún buffer. Si Loyalty Points Service (o la cola que lo alimenta) está caído en ese momento, ese mensaje puntual se pierde para siempre — a diferencia de SQS, que sí lo retendría hasta que el consumidor vuelva a estar disponible.
Ejercicio 9 — Diseñar el patrón SNS + SQS
PaymentApproved debe llegar a 2 consumidores: Invoice Service (genera la factura) y Notification Service (envía el email). Diseñá el patrón SNS + SQS completo: nombre del Topic y de las 2 colas.
🎯 Qué deberías lograr: 1 Topic + 2 colas SQS suscritas a él, con nombres que sigan la convención OrderFlow<Algo>Queue.
💡 ¿Sabías que…? — SNS + SQS no es "elegir uno"
El patrón combina ambos: SNS reparte a quien esté suscrito (fan-out), y cada suscriptor es una cola SQS que retiene el mensaje si su consumidor está ocupado o caído. Es exactamente la arquitectura real de OrderFlow — ver sección 6.
Ver solución
- Topic:
OrderFlowPaymentEvents OrderFlowInvoiceQueue→ alimenta aInvoice Consumer Lambda(genera la factura)OrderFlowNotificationQueue→ alimenta aNotification Consumer Lambda(envía el email) — puede reutilizarse la misma cola que ya usaOrderCreateden la sección 6, si el consumidor solo necesita loguear/notificar
Payment Service publica PaymentApproved una vez en el Topic; no conoce ni le importa cuántos suscriptores hay.
Ejercicio 10 — Tabla comparativa SNS vs. SQS
Completá esta tabla (3 filas, 2 columnas) sin mirar la sección 5: mensajes en tiempo real vs. buffer, cuántos receptores por mensaje, qué pasa si el consumidor está caído.
🎯 Qué deberías lograr: 6 celdas completas, coherentes con la tabla real de la sección 5.
💡 ¿Sabías que…? — pensalo como "megáfono vs. buzón"
SNS es un megáfono: todos los que están escuchando en ese momento reciben el anuncio. SQS es un buzón: el mensaje queda ahí hasta que alguien lo abre, sin importar cuándo. Ver sección 5.
Ver solución
| Dimensión | SNS | SQS |
|---|---|---|
| Mensajes | En tiempo real, no almacena | Buffer persistente |
| Receptores por mensaje | Múltiples (fan-out) | Uno (el consumidor de esa cola) |
| Consumidor caído | El mensaje se pierde | El mensaje espera en la cola |
Ejercicio 11 — Event source mapping
Explicá en una frase qué hace un event source mapping entre una cola SQS y una Lambda, y qué evita que el equipo tenga que programar.
🎯 Qué deberías lograr: tu frase debe mencionar que evita el polling manual.
💡 ¿Sabías que…? — quién hace el polling en realidad
Alguien tiene que estar preguntando "¿hay mensajes nuevos?" — con un event source mapping, ese trabajo lo hace AWS internamente, no tu código. Ver glosario, sección 1.
Ver solución
Un event source mapping configura a Lambda para que AWS la invoque automáticamente apenas hay mensajes nuevos en la cola SQS asociada — evita que el equipo tenga que escribir un loop que consulte la cola por su cuenta (polling manual).
Ejercicio 12 — Escribir el evento StockReserved
Escribí el JSON completo del evento StockReserved para una reserva de 3 unidades del producto PROD-777 correspondiente al pedido ORD-3010.
🎯 Qué deberías lograr: JSON válido con los 4 campos y data incluyendo order_id, product_id y quantity.
💡 ¿Sabías que…? — mismo molde que el Ejercicio 3
Es el mismo ejercicio que el 3, con otro event_type y otro data — el molde de 4 campos no cambia nunca. Ver sección 3.
Ver solución
json
{
"event_id": "7fa1-...",
"event_type": "StockReserved",
"occurred_at": "2026-09-03T20:20:00Z",
"data": {
"order_id": "ORD-3010",
"product_id": "PROD-777",
"quantity": 3
}
}Ejercicio 13 — Analizar un escenario de fallo
Inventory Consumer Lambda se cae durante 10 minutos. Explicá qué pasa con los mensajes que llegan a OrderFlowInventoryQueue durante ese lapso, y qué pasa cuando Lambda vuelve a estar disponible.
🎯 Qué deberías lograr: tu explicación debe concluir que no se pierde ningún mensaje.
💡 ¿Sabías que…? — la cola no sabe (ni le importa) que el consumidor está caído
SQS solo acumula mensajes hasta que alguien los confirma como procesados (ack/delete) — no distingue entre "el consumidor está ocupado" y "el consumidor está caído". Ver sección 5.
Ver solución
Durante los 10 minutos, los mensajes se acumulan en OrderFlowInventoryQueue sin perderse — SQS es un buffer persistente. Notification Consumer Lambda (que tiene su propia cola, sección 6) sigue funcionando normalmente, sin verse afectado. Cuando Inventory Consumer Lambda vuelve a estar disponible, el event source mapping retoma la invocación y procesa los mensajes acumulados en orden de llegada a la cola.
Ejercicio 14 — Agregar un suscriptor sin tocar el publisher
Diseñá cómo agregarías una nueva cola OrderFlowAuditQueue (para un futuro Audit Service) al Topic OrderFlowOrderEvents, sin modificar Order Service. Listá los pasos.
🎯 Qué deberías lograr: una lista de pasos donde Order Service no aparece como algo a modificar.
💡 ¿Sabías que…? — "Extensible sin cambios" no es solo un slogan
Es literalmente el punto 3 de la sección 4: el publisher solo conoce el Topic, nunca a sus suscriptores. Ejemplo de referencia: agregar OrderFlowFraudQueue a OrderFlowPaymentEvents sin tocar Payment Service — mismos pasos, otro caso.
Ver solución
- Crear la cola SQS
OrderFlowAuditQueue. - Suscribir esa cola al Topic
OrderFlowOrderEvents(SNS → SQS subscription). - Configurar el event source mapping entre
OrderFlowAuditQueuey la futuraAudit Consumer Lambda. - Desplegar
Audit Consumer Lambda.
Order Service no aparece en ningún paso — sigue publicando exactamente igual que antes; simplemente ahora hay un suscriptor más escuchando.
Ejercicio 15 — Identificar publisher y suscriptores
En este escenario: Payment Service publica PaymentApproved; Invoice Service e Inventory Service lo consumen para generar la factura y ajustar el stock reservado respectivamente. Identificá quién es el publisher y quiénes son los suscriptores.
🎯 Qué deberías lograr: 1 publisher y 2 suscriptores correctamente identificados.
💡 ¿Sabías que…? — el publisher es siempre el "dueño" del hecho
El publisher es quien tiene la autoridad sobre ese hecho de negocio (solo Payment Service sabe si un pago fue aprobado). Los suscriptores son quienes reaccionan sin ser dueños del hecho. Ver sección 4.
Ver solución
- Publisher:
Payment Service(es el único que sabe si un pago fue aprobado). - Suscriptores:
Invoice ServiceeInventory Service— ambos reaccionan al mismo evento, cada uno con su propia responsabilidad de negocio.
Ejercicio 16 — Acoplamiento temporal aplicado
Comparando Order → Inventory síncrono (llamada HTTP directa) contra Order → SNS asíncrono (publicar un evento): ¿cuál tiene acoplamiento temporal alto y cuál bajo? Justificá.
🎯 Qué deberías lograr: identificar correctamente cuál es alto/bajo, con la palabra "disponibilidad" o "al mismo tiempo" en tu justificación.
💡 ¿Sabías que…? — "temporal" es sobre el tiempo, no sobre datos
Acoplamiento temporal es sobre si ambos servicios necesitan estar disponibles al mismo tiempo para que la comunicación funcione — no sobre si comparten estructura de datos. Ver sección 2 y Clase 5, sección 5.
Ver solución
Order → Inventory síncrono tiene acoplamiento temporal alto: si Inventory está caído en el instante de la llamada, Order falla también — ambos deben estar disponibles al mismo tiempo. Order → SNS asíncrono tiene acoplamiento temporal bajo: Order publica y sigue; el o los consumidores procesan cuando puedan, sin necesidad de estar disponibles en ese instante exacto.
Ejercicio 17 — Justificar cada campo de un evento
Para el evento PaymentApproved del Ejercicio 3, explicá en una línea para qué sirve cada uno de sus 4 campos en ese contexto puntual (el pago).
🎯 Qué deberías lograr: 4 líneas, una por campo, específicas del caso de pago (no la definición genérica).
💡 ¿Sabías que…? — la definición genérica vs. el caso concreto
La tabla de la sección 3 da la definición general de cada campo; acá el ejercicio pide aplicarla al caso concreto de un pago. Ejemplo de referencia aplicado a UserRegistered: event_id evita registrar dos veces al mismo usuario si el mensaje se reintenta.
Ver solución
event_id: evita que, si SQS reintenta la entrega,Invoice Servicegenere dos facturas para el mismo pago aprobado.event_type(PaymentApproved): le dice a cada suscriptor qué pasó, para que decida si le importa reaccionar o no.occurred_at: permite reconstruir, ante una auditoría, el orden real en que se aprobaron los pagos, aunque lleguen desordenados.data(order_id,amount,currency): son los datos concretos queInvoice Servicenecesita para generar la factura correcta.
Ejercicio 18 — Diseño integrador: microservicio Payments
Diseñá cómo un nuevo microservicio Payments se integraría con Orders e Inventory usando EDA: qué evento publica, qué Topic/colas usarías, y quién lo consume.
🎯 Qué deberías lograr: un diseño con 1 evento, 1 Topic, al menos 1 cola y al menos 1 consumidor, coherente con la convención de nombres de OrderFlow.
💡 ¿Sabías que…? — "diseñar" acá es aplicar todo lo visto junto
Este ejercicio combina secciones 3, 4 y 5: nombrar el evento en pasado, definir el Topic y la(s) cola(s), y decidir qué consume cada una. Es el mismo tipo de decisión que ya tomaste en los Ejercicios 7 y 9, con otro servicio.
Ver solución
- Evento:
PaymentApproved, publicado porPayment Servicecuando el cobro se autoriza con éxito. - Topic:
OrderFlowPaymentEvents. - Colas suscritas:
OrderFlowOrderQueue(consumida porOrder Consumer Lambda, que marca el pedido como pagado) yOrderFlowInventoryQueue(consumida porInventory Consumer Lambda, que confirma la reserva de stock como definitiva). Payment Serviceno conoce aOrdersni aInventorydirectamente — solo publica en su Topic.
Ejercicio 19 — Riesgos de no ser idempotente
Si Inventory Consumer Lambda descuenta stock cada vez que reprocesa un mensaje duplicado de StockReserved (sin chequear event_id), explicá el problema con un ejemplo numérico: stock inicial 10, reserva de 2 unidades, mensaje reintentado 3 veces.
🎯 Qué deberías lograr: tu ejemplo debe mostrar un stock final incorrecto (menor al esperado) por el reprocesamiento.
💡 ¿Sabías que…? — SQS puede entregar el mismo mensaje más de una vez
SQS estándar garantiza at-least-once delivery (al menos una vez) — no exactly-once — por diseño puede entregar el mismo mensaje más de una vez ante ciertos reintentos de red. Por eso la idempotencia (Ejercicio 4) no es opcional en el consumidor. Es el mismo riesgo que muestra la captura real de la sección 5 con OrderCreated.
Ver solución
Stock inicial: 10. La reserva real es de 2 unidades, pero el mensaje StockReserved llega 3 veces (1 entrega original + 2 reintentos) y el consumidor descuenta las 2 unidades cada vez que lo recibe, sin chequear si ya lo procesó:
10 - 2 - 2 - 2 = 4
Stock final: 4, cuando el correcto (una sola reserva real de 2 unidades) debería ser 8. El sistema termina bloqueando stock que en realidad está disponible.
Ejercicio 20 — Caso integrador final
Diseñá la arquitectura completa de notificación de un pedido nuevo: el JSON del evento OrderCreated, el Topic, las colas involucradas, qué pasa si Notification Consumer Lambda está caída 10 minutos, y qué campo del evento garantiza que no se procese dos veces.
🎯 Qué deberías lograr: una respuesta que integre las secciones 3 (evento), 4 (SNS), 5 (SQS) y 6 (arquitectura real) sin dejar ningún punto sin responder.
💡 ¿Sabías que…? — este ejercicio es literalmente la sección 6
Si te trabaste, releé la sección 6 — la respuesta es exactamente esa arquitectura, solo que acá te toca redactarla vos con tus propias palabras en vez de leerla.
Ver solución
Evento:
json
{
"event_id": "c4de-...",
"event_type": "OrderCreated",
"occurred_at": "2026-09-03T20:30:00Z",
"data": { "order_id": "ORD-4100", "product_id": "PROD-050", "quantity": 1 }
}Arquitectura: Order Service publica OrderCreated en el Topic SNS OrderFlowOrderEvents, que hace fan-out a dos colas: OrderFlowInventoryQueue (consumida por Inventory Consumer Lambda, que escribe en DynamoDB) y OrderFlowNotificationQueue (consumida por Notification Consumer Lambda, que loguea en CloudWatch).
Si Notification Consumer Lambda está caída 10 minutos: el mensaje se acumula en OrderFlowNotificationQueue sin perderse (SQS es un buffer persistente) y se procesa cuando la Lambda vuelve — mientras tanto, Inventory Consumer Lambda sigue funcionando normal porque tiene su propia cola independiente.
Qué evita el doble procesamiento: event_id — el consumidor lo usa para detectar si ya procesó ese evento y descartar duplicados (idempotencia).
❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué es un evento en una arquitectura orientada a eventos?
Un hecho inmutable que ya ocurrió en el sistema y queda registrado en el tiempo — no es una orden ni una petición.
2. ¿Cuál es la diferencia de nomenclatura entre un comando y un evento?
El comando va en imperativo (
ReserveStock: "hacé esto ahora"); el evento va en pasado (StockReserved: "esto ya ocurrió").
3. ¿Para qué sirve el campo event_id de un evento?
Para garantizar idempotencia — el consumidor lo usa para identificar y descartar duplicados.
4. ¿Qué patrón implementa Amazon SNS?
Fan-out (pub/sub): un mensaje publicado en un Topic se distribuye automáticamente a todos sus suscriptores, sin que el publisher los conozca.
5. ¿Cuál es la diferencia principal entre SNS y SQS?
SNS distribuye en tiempo real y no almacena; SQS almacena (buffer persistente) el mensaje hasta que un consumidor lo procese.
6. ¿Qué pasa si Order Service publica un evento en SNS y el suscriptor está caído en ese momento, sin ninguna cola SQS de por medio?
El mensaje se pierde — SNS no lo almacena para reintentarlo después.
7. ¿Qué es el patrón SNS + SQS y qué problema resuelve?
Una cola SQS se suscribe al Topic SNS y recibe todo lo publicado — combina el fan-out de SNS con el buffer de SQS, evitando perder mensajes si un consumidor está caído.
8. ¿Qué es un event source mapping en AWS Lambda?
La configuración que suscribe una Lambda directamente a una cola SQS para que AWS la invoque automáticamente cuando hay mensajes nuevos, sin polling manual.
9. En la arquitectura de OrderFlow, ¿por qué Inventory Consumer Lambda y Notification Consumer Lambda tienen colas SQS separadas en vez de compartir una sola?
Para que cada consumidor tenga su propio ritmo de procesamiento, gestión de fallos y reintentos — un fallo o retraso en uno no bloquea al otro.
10. ¿Por qué el campo occurred_at sigue siendo necesario aunque cada evento ya tenga un event_id único?
Porque
event_ididentifica y evita duplicados, pero no dice el orden —occurred_ates lo que permite ordenar los eventos cronológicamente aunque lleguen desordenados al consumidor.
📎 Apuntes relacionados
- Clase 5, sección 5 — Comunicación síncrona vs. asíncrona — la base del repaso de la sección 2 de esta clase (ejemplo gRPC/Kafka).
- Clase 8 — Microservicios Serverless con AWS — AWS Lambda y DynamoDB, los mismos servicios que aparecen como consumidores en la arquitectura de OrderFlow de esta clase.
- 00-Notas/02-Conceptos.md — resumen de EDA, evento vs. comando y SNS/SQS enlazado a esta clase.
- Diagrama de arquitectura del patrón fan-out (
04-Recursos/diagramas-tecnicos/clase-09-sns-fan-out/).