Skip to content

📙 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 ​

📖 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érminoQué esSe 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
EventoHecho inmutable que ya ocurrió en el sistema y queda registrado en el tiempo — no es una orden ni una petición.sección 3
ComandoInstrucció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_idIdentificador único de cada evento; clave para garantizar idempotencia al consumirlo (detectar y descartar duplicados).sección 3
occurred_atTimestamp del evento; permite ordenar los eventos cronológicamente aunque lleguen desordenados al consumidor.sección 3
IdempotenciaPropiedad 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íncrona y Acoplamiento temporal ya 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érminoQué esSe 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-outPatró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
PublisherEl componente que publica el mensaje en el Topic (en el ejemplo, Order Service).sección 4
Patrón SNS + SQSUna 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 mappingConfiguració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 deliveryGarantí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 responseCuando 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) ​

Slide "Comunicación síncrona vs. asíncrona": a la izquierda, 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; a la derecha, Asíncrono — Orders publica un evento en una cola y puede continuar sin esperar, Inventory procesa el mensaje cuando está disponible sin bloquear al productor. Abajo, tabla comparativa por dimensión (espera de respuesta, acoplamiento temporal, mecanismo, resultado) y un callout aclarando que asíncrono no significa automáticamente "más rápido"

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ónSíncronoAsíncrono
Espera de respuestaSí, siempreNo necesariamente
Acoplamiento temporalAltoBajo
MecanismoHTTP directoMensajes / eventos
ResultadoInmediatoProcesamiento 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 ​

Slide "¿Qué es un evento?": a la izquierda, un JSON de ejemplo con event_id, event_type "OrderCreated", occurred_at "2026-09-02T20:00:00Z" y un objeto data con order_id, product_id y quantity; a la derecha, dos tarjetas comparando Comando (ReserveStock, "Haz esto ahora") vs Evento (OrderCreated, "Esto ya ocurrió"), y una lista de ejemplos de eventos (OrderCreated, PaymentApproved, StockReserved, UserRegistered)

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
  }
}
CampoPara qué sirve
event_idIdentifica el evento de forma única — garantiza idempotencia (sección 1).
event_typeNombre del hecho ocurrido, en pasado (OrderCreated, no CreateOrder).
occurred_atPermite ordenar los eventos cronológicamente.
dataEl "payload": los datos concretos del hecho (qué pedido, qué producto, cuánto).

🆚 Comando vs. Evento ​

ComandoEvento
EjemploReserveStockOrderCreated
Intención"Haz esto ahora""Esto ya ocurrió"
Quién lo disparaAlguien que necesita que algo paseEl sistema, al registrar un hecho consumado
Se puede rechazarSí (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 ​

Slide "Amazon SNS: uno publica, muchos reciben": tres pasos ilustrados — Publisher (Order Service publica OrderCreated en el Topic OrderFlowOrderEvents, un único punto de publicación), Fan-out automático (SNS distribuye el mensaje a cada suscriptor registrado: Inventory Queue, Notification Queue, Analytics, etc.) y Extensible sin cambios (si mañana se añade Analytics Service, Orders no se modifica, solo se agrega un nuevo suscriptor al Topic). Abajo, una nota: una cola SQS puede suscribirse a un Topic SNS y recibir automáticamente todos los mensajes publicados en él — el patrón SNS + SQS que se usará en OrderFlow

PasoQué pasa
1. PublisherOrder Service publica OrderCreated en el Topic OrderFlowOrderEvents — un único punto de publicación.
2. Fan-out automáticoSNS distribuye el mensaje a cada suscriptor registrado: Inventory Queue, Notification Queue, Analytics, etc.
3. Extensible sin cambiosSi 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, Orders solo 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) ​

Diagrama de arquitectura: Order Service (Publisher) publica el evento OrderCreated en el Topic SNS OrderFlowOrderEvents, que lo distribuye automáticamente por fan-out a dos suscriptores reales — Inventory Queue y Notification Queue (ambas colas SQS) — mientras Analytics Service aparece con borde punteado como futuro suscriptor que podría agregarse sin modificar Order Service

📥 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 ​

Slide "Amazon SQS: la cola que desacopla": a la izquierda, "¿Qué aporta SQS?" — SQS actúa como buffer persistente entre productor y consumidor, si Inventory Lambda está temporalmente detenida los mensajes permanecen en la cola esperando, no se pierden — con tres flechas apiladas "SNS publica → SQS almacena → Lambda procesa"; a la derecha, "SNS vs. SQS: diferencia clave" con dos tarjetas (SNS → Distribución: entrega a múltiples suscriptores en tiempo real, no almacena mensajes; SQS → Cola: almacena mensajes hasta que un consumidor los procese, ideal para resiliencia) y una nota: Lambda puede configurarse para consumir mensajes de SQS directamente mediante un event source mapping, sin necesidad de polling manual

🆚 SNS vs. SQS: diferencia clave ​

SNS → DistribuciónSQS → Cola
Qué haceEntrega 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 flujoEl 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 ​

Slide "¿Qué pasa cuando algo falla?": 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 — con 4 flechas apiladas "Lambda falla → Mensaje en SQS → Consumidor reintenta → Procesado exitoso"; abajo, un callout "El problema sin idempotencia": si procesamos OrderCreated (quantity=2) dos veces — stock -2 y luego stock -2 de nuevo — reservamos el doble del stock necesario

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 -2 y luego stock -2 de 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) ​

Diagrama swimlane "Arquitectura completa de OrderFlow" con 4 carriles — Servicio de Pedidos: Order Service publica OrderCreated en el Topic SNS OrderFlowOrderEvents; Cola de Mensajes: SNS distribuye (fan-out) a dos colas SQS, OrderFlowInventoryQueue y OrderFlowNotificationQueue; Consumidores Lambda: OrderFlowInventoryQueue alimenta a Inventory Consumer Lambda, que escribe en DynamoDB; Destinos: OrderFlowNotificationQueue alimenta a Notification Consumer Lambda, que registra logs en CloudWatch

CarrilComponenteQué hace
Servicio de PedidosOrder ServicePublica el evento OrderCreated en el Topic SNS OrderFlowOrderEvents.
Cola de MensajesOrderFlowOrderEvents (Topic SNS)Hace fan-out a dos colas SQS: OrderFlowInventoryQueue y OrderFlowNotificationQueue.
Consumidores LambdaInventory Consumer LambdaSe alimenta de OrderFlowInventoryQueue (event source mapping) y escribe en DynamoDB.
DestinosNotification Consumer LambdaSe 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 mapping de la sección 5, aplicados con los nombres reales del proyecto: la sección 4 usaba Inventory Queue/Notification Queue como ejemplo genérico — acá son literalmente OrderFlowInventoryQueue y OrderFlowNotificationQueue.

⚠️ Cada consumidor tiene su propia cola: si Inventory Consumer Lambda falla o se satura, sus reintentos no afectan en nada a Notification 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

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:Publish sobre 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.

Consumidorprocess_message extraeHace
Inventory Consumerproduct_id, quantityReserva stock en DynamoDB
Notification Consumerorder_id, user_idEnvía notificación al cliente

💡 El productor no conoce la implementación de sus consumidores. Order Service no sabe que Inventory Consumer usa DynamoDB ni que Notification Consumer enví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 Delivery es obligatorio (sección 9, no opcional), y la tabla DynamoDB OrderFlowInventory de 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 el messageId de 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 habilitado ReportBatchItemFailures en 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"
  }]
}
LambdaPermiso mínimoSobre qué recurso puntual
publishersns:PublishSolo el Topic OrderFlowOrderEvents
inventory_consumerdynamodb:GetItem, dynamodb:UpdateItemSolo 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:

Formulario "Publish message" del Topic OrderFlowOrderEvents con Message structure en "Identical payload for all delivery protocols" y el campo Message body con el order_created_input.json crudo (sin envolver), antes de editarlo

Mismo formulario con el Message body ya editado a la forma completa del domain_event: event_id "EVT-001", event_type "OrderCreated", y data con order_id, user_id, product_id, quantity y unit_price

💡 Publicar el domain_event completo a mano (en vez de solo el order_created_input.json crudo) prueba los consumidores de punta a punta — inventory_consumer y notification_consumer esperan message["event_type"] y message["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 ​

Slide "Reto práctico: construye OrderFlow Event-Driven" con la lista de evidencias que debe demostrar cada equipo (1. Publicar OrderCreated desde Order Service, 2. InventoryQueue y NotificationQueue reciben el evento, 3. Inventory Consumer actualiza DynamoDB, 4. Notification Consumer registra log en CloudWatch, 5. Detener un consumer y ver el mensaje en espera en SQS, 6. Reactivar el consumer y comprobar el procesamiento) y a la derecha una tarjeta "El salto de esta sesión" comparando Sesión 8 (HTTP → Lambda, síncrono y acoplado) con Sesión 9 (EVENTO → SNS/SQS → Lambda, asíncrono y desacoplado), con la nota de que este patrón es la base de sistemas resilientes, escalables y extensibles en producción

Evidencias que debe demostrar cada equipo:

  1. Publicar OrderCreated desde Order Service.
  2. InventoryQueue y NotificationQueue reciben el evento.
  3. Inventory Consumer actualiza DynamoDB.
  4. Notification Consumer registra log en CloudWatch.
  5. Detener un consumer y ver el mensaje en espera en SQS.
  6. 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 8Sesión 9
FlujoHTTP → LambdaEVENTO → SNS / SQS → Lambda
AcoplamientoSíncrono y acopladoAsí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) ​

Consola de AWS Billing and Cost Management, pantalla "Create budget" con el panel "Choose budget type": Budget setup en "Use a template (simplified)" y, dentro de Templates, el template "Zero spend budget" seleccionado ("Create a budget that notifies you once your spending exceeds $0.01"), con el campo Budget name "My Zero-Spend Budget" y el campo Email recipients vacío

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 ​

Buscador de la consola de AWS con "sns" tipeado, mostrando el resultado "Simple Notification Service — SNS managed message topics for Pub/Sub" resaltado entre los servicios sugeridos

📋 Crear el Topic ​

Página de inicio de Amazon SNS ("Pub/sub messaging for microservices and serverless applications") con el panel lateral "Create topic": campo Topic name con placeholder "MyTopic" y botón "Next step"

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

Formulario "Create topic" de Amazon SNS con la opción de tipo FIFO (first-in, first-out) resaltada — orden estrictamente preservada, entrega exactly-once, solo soporta SQS como protocolo de suscripción — junto a la opción Standard sin seleccionar; abajo, el campo Name con placeholder "MyTopic" y sufijo ".fifo" obligatorio para topics FIFO

Mismo formulario "Create topic" con Standard ahora seleccionado — orden best-effort, entrega at-least-once, soporta SQS, Lambda, Data Firehose, HTTP/S, SMS, email y endpoints de aplicaciones móviles como protocolos de suscripción — y los campos Name y Display name (opcional) debajo, más las secciones plegables Encryption, Access policy y Delivery policy (HTTP/S)

SNS FIFOSNS Standard
OrdenEstrictamente preservadoBest-effort
EntregaExactly-onceAt-least-once
Suscriptores permitidosSolo colas SQSSQS, Lambda, Data Firehose, HTTP/S, SMS, email, apps móviles

⚠️ Para OrderFlow se elige Standard, no FIFO — porque Notification Consumer necesita 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 ​

Consola de Amazon SNS confirmando "Topic OrderFlowOrderEvents created successfully" con los detalles del topic: Name OrderFlowOrderEvents, ARN arn:aws:sns:us-east-2:540659180627:OrderFlowOrderEvents, Type Standard, Topic owner 540659180627, y la pestaña Subscriptions (0) vacía con el botón "Create subscription"

CampoValor
NameOrderFlowOrderEvents
ARNarn:aws:sns:us-east-2:540659180627:OrderFlowOrderEvents
TypeStandard
Subscriptions0 (todavía sin colas suscritas)

📥 Crear la primera cola SQS: OrderFlowInventoryQueue ​

Buscador de la consola de AWS con "sqs" tipeado, mostrando el resultado "Simple Queue Service — SQS Managed Message Queues" resaltado

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):

Formulario "Create queue" de Amazon SQS con Standard seleccionado ("At-least-once delivery, message ordering isn't preserved" — At-least once delivery, Best-effort ordering) frente a FIFO ("First-in-first-out delivery, message ordering is preserved" — First-in-first-out delivery, Exactly-once processing); abajo, un aviso "You can't change the queue type after you create a queue" y el campo Name con el valor "OrderFlowInventoryQueue"

Consola de Amazon SQS mostrando la cola OrderFlowInventoryQueue ya creada, con sus Details: Type Standard, Encryption Amazon SQS key (SSE-SQS), ARN arn:aws:sqs:us-east-2:540659180627:OrderFlowInventoryQueue, URL https://sqs.us-east-2.amazonaws.com/540659180627/OrderFlowInventoryQueue, y las pestañas Queue policies, Monitoring, SNS subscriptions, Lambda triggers, EventBridge Pipes, Dead-letter queue, Tagging, Encryption, con el JSON del Access policy por defecto visible abajo

CampoValor
NameOrderFlowInventoryQueue
TypeStandard
ARNarn:aws:sqs:us-east-2:540659180627:OrderFlowInventoryQueue
EncryptionAmazon 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 ​

Formulario "Create queue" de Amazon SQS con Standard seleccionado, el campo Name con el valor "OrderFlowNotificacionQueue", y la sección Configuration con Visibility timeout 30 Seconds, Delivery delay 0 Seconds, Message retention period 4 Days y Maximum message size 1024 KiB

ConfiguraciónValor
Visibility timeout30 segundos
Delivery delay0 segundos
Message retention period4 días
Maximum message size1024 KiB

📝 Corrección de la captura: el nombre tipeado en la consola fue OrderFlowNotificacionQueue (con "Notificacion" en español) — no OrderFlowNotificationQueue, 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.

Confirmación "Queue OrderFlowNotificacionQueue created successfully" con los detalles: Name OrderFlowNotificacionQueue, Type Standard, ARN arn:aws:sqs:us-east-2:540659180627:OrderFlowNotificacionQueue, Encryption Amazon SQS key (SSE-SQS), URL https://sqs.us-east-2.amazonaws.com/540659180627/OrderFlowNotificacionQueue

🔗 Suscribir las colas al Topic ​

Buscador de la consola de AWS con "sns" tipeado de nuevo, mostrando "Simple Notification Service" resaltado — volviendo al Topic para suscribir las dos colas SQS recién creadas

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.

Página del Topic OrderFlowOrderEvents con la pestaña Subscriptions (0) vacía, mostrando el botón "Create subscription"

Formulario "Create subscription" con el Topic ARN de OrderFlowOrderEvents ya cargado, el campo Protocol en "Select protocol" sin elegir todavía, y el aviso "After your subscription is created, you must confirm it"

Mismo formulario con Protocol = "Amazon SQS" seleccionado, y el campo Endpoint mostrando el placeholder de ejemplo arn:aws:sqs:us-east-1:123456789012:MyQueue, con la nota "Only Amazon SQS standard queues will be listed and can receive notifications from an Amazon SNS standard topic"

Formulario completo con el Endpoint apuntando al ARN real de OrderFlowNotificacionQueue, y el checkbox "Enable raw message delivery" sin marcar todavía

Detalle del checkbox "Enable raw message delivery" ya marcado, con el aviso de que la suscripción debe confirmarse después de creada

⚠️ Enable raw message delivery no 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 como TopicArn, 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 espera json.loads(record["body"]) en el código ya visto. Sin esta opción, ese json.loads funcionaría pero traería el sobre de SNS, no el evento — habría que hacer un json.loads extra sobre el campo "Message".

Ambas suscripciones, confirmadas:Topic OrderFlowOrderEvents con Subscriptions (2): dos filas con status "Confirmed" y protocolo SQS, apuntando a los endpoints OrderFlowN... (Notification) y OrderFlowIn... (Inventory)

💡 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:

Página "Edit subscription" de la suscripción hacia OrderFlowInventoryQueue, mostrando Topic, Protocol (Amazon SQS), Endpoint, y el checkbox "Enable raw message delivery" todavía sin marcar, con el botón "Save changes"

✅ 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:

Confirmación verde "Message published to topic OrderFlowOrderEvents successfully" con Message ID y Request ID, y debajo la lista de Subscriptions (2) — ambas con status Confirmed y protocolo SQS

Lista de Queues (2) en la consola de SQS: OrderFlowInventoryQueue y OrderFlowNotificacionQueue, ambas Type Standard, ambas con "Messages available: 1" y "Messages in flight: 0"

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).

Página "Create function" de AWS Lambda con "Author from scratch" seleccionado (frente a "Use a blueprint" y "Container image"), y el panel lateral "Tutorials" abierto mostrando "Create a simple web app" como sugerencia

Sección "Basic information" del formulario, con el campo Function name en "orderflow-order-publisher" — siguiendo la misma convención de nombres orderflow-* que las Lambdas de Clase 8 (orderflow-inventory)

CampoValor
MétodoAuthor from scratch
Function nameorderflow-order-publisher
RuntimePython 3.12
PermissionsRol 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-publisher sigue la misma convención orderflow-* 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.

Formulario "Edit environment variables" de la Lambda orderflow-order-publisher, con Key "TOPIC_ARN" y Value "arn:aws:sns:us-east-2:540659180627:OrderFlowOrderEvents" pegado desde el ARN copiado de la consola de SNS

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

💡 El código de lambda_function.py en 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"

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 ​

Formulario "Create function" con Author from scratch seleccionado, Function name "orderflow-inventory-consumer", Runtime Python 3.12, y la sección "Custom settings" (Durable execution y EC2 capacity provider, ambos apagados) antes de hacer clic en Create function

Página de la función recién creada con el banner "Successfully created the function orderflow-inventory-consumer", el diagrama Function overview mostrando el nodo Lambda con Layers (0) y los botones Add trigger / Add destination (todavía sin ninguno), el Function ARN, y la pestaña Configuration → Environment variables (0) — sin variables todavía

CampoValor
Function nameorderflow-inventory-consumer
RuntimePython 3.12
TriggerNinguno todavía — falta Add trigger apuntando a OrderFlowInventoryQueue
Environment variablesNinguna 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 OrderFlowInventoryQueue para 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

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).

Formulario "Add trigger" de orderflow-inventory-consumer con el dropdown "Trigger configuration" en "Select a source", todavía sin elegir ninguno

Mismo dropdown con "sqs" tipeado en el buscador, mostrando el resultado "SQS" bajo la categoría "Batch/bulk data processing" con las etiquetas aws · event-source-mapping · polling · queue

💡 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.

Campo "SQS queue" con el ARN de OrderFlowInventoryQueue ya cargado (arn:aws:sqs:us-east-2:540659180627:OrderFlowInventoryQueue), y el inicio de la sección "Event poller configuration" debajo

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:

Pestaña Permissions de orderflow-inventory-consumer mostrando el Execution role "orderflow-inventory-consumer-role-ky9ddi27" y su Resource summary: únicamente Amazon CloudWatch Logs (3 actions, 3 resources) — sin permisos de DynamoDB todavía

Trigger confirmado:Banner verde "The trigger OrderFlowInventoryQueue was successfully added to function orderflow-inventory-consumer", con el diagrama Function overview ahora mostrando un nodo SQS conectado a la Lambda, y la pestaña Triggers (1) listando SQS: OrderFlowInventoryQueue con state "Creating"

⚠️ El trigger queda en estado Creating un momento antes de pasar a Enabled — es normal, AWS tarda unos segundos en activar el poller interno que vigila la cola.

🚀 Tercera Lambda: orderflow-notification-consumer ​

Formulario "Create function" con Function name "orderflow-notification-consumer", Runtime Python 3.12, Permissions con el rol de ejecución por defecto, y la sección Custom settings (Durable execution y EC2 capacity provider apagados) antes de crear la función

CampoValor
Function nameorderflow-notification-consumer
RuntimePython 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:Banner verde "The trigger OrderFlowNotificacionQueue was successfully added to function orderflow-notification-consumer", con el diagrama Function overview mostrando el nodo SQS conectado, y la pestaña Triggers (1) listando SQS: OrderFlowNotificacionQueue con state "Creating"

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:

Página "Edit item" de DynamoDB para la tabla OrderFlowInventory, mostrando el ítem con partition key product_id "PROD-001" y los atributos available_stock (Number, 20), product_name (String, "Laptop empresarial") y reserved_stock (Number, 0)

AtributoValor antes de la prueba
product_id (partition key)PROD-001
available_stock20
reserved_stock0
product_nameLaptop empresarial

💡 Mismo esquema documentado en Clase 8 — nada nuevo que crear acá, solo confirmar el punto de partida. Con el order_created_input.json de prueba (product_id: "PROD-001", quantity: 2), el resultado esperado después de que inventory_consumer procese el evento es available_stock: 18 y reserved_stock: 2 — el mismo ConditionExpression de 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).

Página IAM "Attach policy to orderflow-order-publisher-role-i2i0t6c9", con "sns" tipeado en el buscador de "Other permissions policies" y 5 resultados de policies administradas por AWS que contienen SNS: AmazonSNSFullAccess, AmazonSNSReadOnlyAccess, AmazonSNSRole, AWSElasticBeanstalkRoleSNS, AWSIoTDeviceDefenderPublishFindingsToSNSMitigationAction — ninguna todavía seleccionada

⚠️ 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 solo OrderFlowOrderEvents. Esto contradice el principio de mínimo privilegio ya aplicado en Clase 8 y en la propia iam/publisher-policy.json del proyecto (sección 7), que solo permite sns:Publish sobre un Topic puntual. Para production, conviene crear/adjuntar una policy propia (como publisher-policy.json) en vez de una managed policy tan amplia — usar AmazonSNSFullAccess acá 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

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 name de IAM solo acepta alfanuméricos y +=,.@_-). Caso real documentado en 06-Errores: IAM policyName inválido. En ese momento la policy quedó creada como orderflow-order-publisher-role — más adelante en esta misma sección aparece con el nombre OrderPublishEvents, 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:

Formulario "Configure test event" de orderflow-order-publisher, con Invocation type en Synchronous, Event name "TestPublisher", Event sharing settings en Private, Template "Hello World", y el Event JSON todavía con el placeholder de ejemplo ({"key1": "value1", "key2": "value2", "key3": "value3"}) sin reemplazar

CampoValor
Invocation typeSynchronous — espera la respuesta de la Lambda, hasta 15 minutos de timeout
Event nameTestPublisher
Event sharingPrivate — solo visible para quien lo creó
Event JSONPlaceholder Hello World todavía — falta reemplazarlo por el order_created_input.json real (sección 7)

💡 El lambda_handler del publisher valida contra event["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:

Pestaña Test de orderflow-order-publisher con el banner verde "Executing function: succeeded (logs)", la sección Response mostrando el JSON {"statusCode": 202, "body": "{message: Evento publicado, event_id: 121ab3e7-9d78-47c0-be2d-a09128a0de05, sns_message_id: 680180a5-9c56-525a-84ae-e3b1b1cf5bf2}"}, y el Summary con Execution time "12 seconds ago" y Request ID

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:

Rol IAM orderflow-order-publisher-role-i2i0t6c9 con 2 Permissions policies: AWSLambdaBasicExecutionRole-... (Customer managed) y OrderPublishEvents (Customer inline, 0 attached entities)

📝 Nota sobre el nombre de la policy: acá aparece como OrderPublishEvents, distinto de orderflow-order-publisher-role (el nombre con el que se creó más arriba en esta sección, tras el error de policyName inválido). El contenido es el mismo (sns:Publish sobre OrderFlowOrderEvents) — 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
NombreTipoPor qué
ChargeCardComandoImperativo — "cobrá la tarjeta ahora"
CardChargedEventoPasado — "la tarjeta ya fue cobrada"
CancelOrderComandoImperativo — "cancelá el pedido ahora"
OrderCancelledEventoPasado — "el pedido ya fue cancelado"
SendInvoiceComandoImperativo — "enviá la factura ahora"
InvoiceSentEventoPasado — "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
ComandoEvento resultante
UpdateInventoryInventoryUpdated
RegisterUserUserRegistered
ApprovePaymentPaymentApproved
ShipOrderOrderShipped

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
  1. PaymentApproved — 20:09:30
  2. OrderCreated — 20:10:00
  3. StockReserved — 20:10:15
  4. OrderShipped — 08:00:00 del 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:

  1. Order Service valida que el stock alcance antes de confirmar el pedido.
  2. Order Service avisa que el pedido fue enviado, para que se le mande un email.
  3. Payment Service verifica el saldo de una tarjeta antes de autorizar el cobro.
  4. Analytics Service registra 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
  1. Síncrono — Order Service necesita la respuesta de stock ya para decidir si confirma o rechaza el pedido; no puede seguir sin saberlo.
  2. Asíncrono — nadie necesita esperar el email para que el pedido quede marcado como enviado; es el patrón OrderShipped → Notification Queue de la sección 6.
  3. Síncrono — Payment Service necesita saber si hay fondos antes de decidir si autoriza el cobro.
  4. Asíncrono — la auditoría no bloquea nada del flujo de negocio; puede procesarse cuando Analytics Service pueda.

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 → consume Welcome Email Service
  • OrderFlowLoyaltyQueue → consume Loyalty Points Service
  • OrderFlowAnalyticsQueue → consume Analytics 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 a Invoice Consumer Lambda (genera la factura)
  • OrderFlowNotificationQueue → alimenta a Notification Consumer Lambda (envía el email) — puede reutilizarse la misma cola que ya usa OrderCreated en 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ónSNSSQS
MensajesEn tiempo real, no almacenaBuffer persistente
Receptores por mensajeMúltiples (fan-out)Uno (el consumidor de esa cola)
Consumidor caídoEl mensaje se pierdeEl 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
  1. Crear la cola SQS OrderFlowAuditQueue.
  2. Suscribir esa cola al Topic OrderFlowOrderEvents (SNS → SQS subscription).
  3. Configurar el event source mapping entre OrderFlowAuditQueue y la futura Audit Consumer Lambda.
  4. 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 Service e Inventory 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 Service genere 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 que Invoice Service necesita 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 por Payment Service cuando el cobro se autoriza con éxito.
  • Topic: OrderFlowPaymentEvents.
  • Colas suscritas: OrderFlowOrderQueue (consumida por Order Consumer Lambda, que marca el pedido como pagado) y OrderFlowInventoryQueue (consumida por Inventory Consumer Lambda, que confirma la reserva de stock como definitiva).
  • Payment Service no conoce a Orders ni a Inventory directamente — 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_id identifica y evita duplicados, pero no dice el orden — occurred_at es lo que permite ordenar los eventos cronológicamente aunque lleguen desordenados al consumidor.

📎 Apuntes relacionados ​

➡️ Siguiente ​

Clase 10