El fan-out de 07-02 funciona, però té un sostre que ja es nota. mercadofresco-pedido-confirmado transporta un únic tipus de fet; per a PedidoCancelado, StockBajo i RepartoAsignado caldria un tema per a cadascun, cadascun amb les seves subscripcions, les seves polítiques de cua i els seus filtres. Quatre temes avui, dotze l'any vinent, i un mapa de subscripcions que ningú no sap dibuixar sencer. A més hi ha fets que interessen i que la botiga no publica —que una instància del grup asg-mercadofresco-tienda canviï d'estat, que Trusted Advisor detecti un recurs ociós, que un desplegament falli— i hi ha una ferida oberta: quan la cua d'analítica va estar mal configurada, els esdeveniments d'aquelles hores es van perdre per sempre.

Amazon EventBridge és el bus d'esdeveniments sense servidor d'AWS. En lloc d'un canal per tipus de fet, hi ha un bus on tot es publica, i regles que decideixen on va cada esdeveniment mirant-ne el contingut complet. L'emissor no coneix ningú; el receptor no depèn de l'emissor; el que els uneix és un patró JSON que es pot canviar sense desplegar codi. I de regal, EventBridge rep de fàbrica els esdeveniments de més de 200 serveis d'AWS i pot arxivar i reproduir tot el que hi passa.

En aquesta lliçó el Luis crea bus-mercadofresco, dissenya el catàleg d'esdeveniments de negoci de la botiga, escriu patrons de complexitat creixent, connecta una destinació d'API cap a l'ERP del magatzem, programa la càrrega nocturna a Redshift amb cron, i per fi explota els Streams de mercadofresco-carritos que van quedar presentats i sense fer servir a 06-02.

Avís de cost. EventBridge cobra 1,00 USD per milió d'esdeveniments personalitzats publicats; els esdeveniments de serveis d'AWS al bus per defecte són gratuïts. L'arxiu costa per GB emmagatzemat i la reproducció es cobra com una publicació nova: reproduir un mes d'esdeveniments torna a passar per caixa. Pipes i Scheduler tenen la seva pròpia tarifa. Al final tens la neteja. Dades fictícies.

Contingut

  1. SNS o EventBridge: la comparació honesta
  2. Busos: per defecte, personalitzat i de socis
  3. Anatomia d'un esdeveniment
  4. El catàleg d'esdeveniments de MercadoFresco
  5. Regles i patrons d'esdeveniment
  6. Destinacions i transformació de l'entrada
  7. Reintents, edat màxima de l'esdeveniment i DLQ per destinació
  8. Esdeveniments dels serveis d'AWS
  9. Regles programades i EventBridge Scheduler
  10. Registre d'esquemes i enllaços de codi
  11. Arxiu i reproducció d'esdeveniments
  12. EventBridge Pipes i els Streams de mercadofresco-carritos
  13. Arquitectura dirigida per esdeveniments: contractes i versionatge
  14. Observabilitat i cost
  15. Errors habituals i consells
  16. Exercicis
  17. Conclusió

SNS o EventBridge: la comparació honesta

Tots dos lliuren un missatge a diverses destinacions. La diferència està en què saben del contingut i en què porten posat de fàbrica.

Amazon SNS Amazon EventBridge
Unitat Tema per tipus de missatge Un bus per a molts tipus
Filtratge Atributs o cos, regles senzilles Patró sobre tot l'esdeveniment, imbricat, arrays
Destinacions per regla/subscripció 1 Fins a 5
Orígens d'AWS Només els que publiquen explícitament 200+ serveis de fàbrica
Orígens SaaS No Busos de socis (Datadog, Shopify, Zendesk…)
Esquemes No Registre i descobriment automàtic
Arxiu i reproducció No
Transformació de l'entrada No Sí (input transformer)
Latència típica Desenes de ms Centenars de ms (~0,5 s)
Rendiment Molt alt (>100.000/s) 10.000 publicacions/s per defecte (ampliable)
Cost per milió 0,50 USD publicar + lliuraments 1,00 USD publicar, lliuraments inclosos
Correu, SMS, push No

El criteri pràctic. Fes servir SNS quan el patró sigui fan-out pur d'un fet a moltes destinacions amb filtres simples, quan importi la latència mínima, quan el volum sigui molt alt o quan la destinació sigui una persona (correu, SMS). Fes servir EventBridge quan hi hagi diversos tipus d'esdeveniment sobre un mateix canal, quan l'encaminament depengui del contingut, quan vulguis reaccionar a esdeveniments d'AWS o d'un SaaS, quan necessitis arxiu i reproducció, o quan la destinació sigui una API externa.

MercadoFresco acaba fent servir tots dos, i no és cap contradicció: bus-mercadofresco per als esdeveniments de negoci i per a tot el que vingui d'AWS, i SNS per al fan-out d'alt volum de mercadofresco-pedido-confirmado —que ja funciona i té menys latència— i per als avisos a persones d'alertas-mercadofresco. De fet, un tema d'SNS pot ser destinació d'una regla d'EventBridge, cosa que permet combinar-los sense duplicar res.

Busos: per defecte, personalitzat i de socis

Un bus d'esdeveniments és un canal amb nom. N'hi ha tres classes:

  • Bus per defecte (default). Existeix a cada regió sense crear-lo. Aquí arriben automàticament els esdeveniments dels serveis d'AWS del compte: canvis d'estat d'EC2, resultats de Trusted Advisor, transicions de tasques d'ECS, fallades de desplegament. També accepta esdeveniments propis, però barrejar-los amb el soroll d'AWS complica les regles i l'arxiu.
  • Bus personalitzat. El que crea la teva aplicació per als seus esdeveniments de negoci. Permet polítiques d'accés pròpies, arxiu propi i límits propis.
  • Bus de soci (partner event bus). El crea un SaaS associat per enviar-te els seus esdeveniments sense que muntis webhooks: la passarel·la de pagament, l'ERP al núvol o l'eina de suport.
export PERFIL="--profile mercadofresco-dev --region eu-west-1"

aws events create-event-bus --name bus-mercadofresco \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=integracion Key=Propietario,Value=marta \
         Key=CentroCoste,Value=tecnologia $PERFIL

MercadoFresco separa clarament: bus-mercadofresco per als esdeveniments que publica l'aplicació, i el bus default per reaccionar al que fa AWS.

Anatomia d'un esdeveniment

Tot esdeveniment d'EventBridge té el mateix sobre. Els camps que controles tu són quatre; la resta els posa el servei.

{
  "version": "0",
  "id": "3f8a1c22-9e7d-4b10-a5f1-6c2b0e4d7a19",
  "detail-type": "PedidoConfirmado",
  "source": "mercadofresco.tienda",
  "account": "111122223333",
  "time": "2026-08-02T18:41:07Z",
  "region": "eu-west-1",
  "resources": ["arn:aws:rds:eu-west-1:111122223333:cluster:aurora-mercadofresco-pedidos"],
  "detail": {
    "version": 1,
    "pedido_id": "PED-2026-084417",
    "cliente_id": "CLI-30912",
    "importe_eur": 48.20,
    "franja_entrega": "24h",
    "provincia": "Barcelona",
    "lineas": [
      { "sku": "FRUT-FRES-011", "unidades": 2, "precio_eur": 3.10 },
      { "sku": "PESC-FRES-002", "unidades": 1, "precio_eur": 12.40 }
    ],
    "confirmado_en": "2026-08-02T18:41:07Z"
  }
}
Camp Qui el posa Per a què serveix
source Tu Espai de noms de l'emissor. Convenció: <empresa>.<domini>
detail-type Tu Què ha passat. És el camp que més es fa servir per encaminar
detail Tu La càrrega útil, JSON lliure
resources Tu ARN dels recursos implicats; permet filtrar per recurs
id, time, region, account EventBridge Metadades; time serveix per a l'arxiu i la reproducció

Dos advertiments sobre el detail. Primer: l'esdeveniment complet no pot passar de 256 KB, igual que un missatge d'SQS; si el detall és gran, desa a S3 i envia la referència. Segon: detail és un contracte amb tercers, no una estructura interna. Hi tornarem al final de la lliçó.

El catàleg d'esdeveniments de MercadoFresco

Abans d'escriure una sola regla, la Marta i el Luis escriuen el catàleg. És un document d'una pàgina, i és la peça de govern més rendible de tot el mòdul.

source detail-type Quan s'emet Camps clau del detail
mercadofresco.tienda PedidoConfirmado Cobrament correcte i comanda escrita a Aurora pedido_id, cliente_id, importe_eur, franja_entrega, provincia, lineas
mercadofresco.tienda PedidoCancelado El client o el sistema anul·len una comanda pedido_id, motivo, importe_devuelto_eur
mercadofresco.almacen StockBajo Un SKU baixa del llindar de reposició sku, unidades_restantes, umbral, proveedor_id
mercadofresco.reparto RepartoAsignado S'assigna repartidor i franja pedido_id, repartidor_id, franja, eta

Tres convencions que estalvien molt de dolor. El detail-type va en passat i descriu un fet, no una ordre: PedidoConfirmado, no ConfirmarPedido; si descriu una ordre, has tornat a l'acoblament que volies evitar. El source identifica el domini emissor, no l'equip ni el servei tècnic, perquè sobrevisqui a reorganitzacions. I el detail porta sempre version, que és l'única cosa que permetrà fer evolucionar el contracte sense trencar consumidors.

Publicar és una crida:

import json, boto3
from datetime import datetime, timezone

eb = boto3.client("events", region_name="eu-west-1")


def publicar_comanda_confirmada(comanda):
    resposta = eb.put_events(Entries=[{
        "EventBusName": "bus-mercadofresco",
        "Source": "mercadofresco.tienda",
        "DetailType": "PedidoConfirmado",
        "Time": datetime.now(timezone.utc),
        "Resources": ["arn:aws:rds:eu-west-1:111122223333:cluster:aurora-mercadofresco-pedidos"],
        "Detail": json.dumps({"version": 1, **comanda}, ensure_ascii=False),
    }])
    # put_events retorna 200 encara que falli alguna entrada: cal mirar FailedEntryCount.
    if resposta["FailedEntryCount"]:
        for e in resposta["Entries"]:
            if "ErrorCode" in e:
                log.error("esdeveniment no publicat",
                          extra={"code": e["ErrorCode"], "msg": e["ErrorMessage"]})
    return resposta

put_events accepta fins a 10 entrades per crida i té el mateix parany que send_message_batch de 07-01: 200 amb fallades parcials a dins. Si no comproves FailedEntryCount, perds esdeveniments en silenci.

Regles i patrons d'esdeveniment

Una regla té un patró i fins a cinc destinacions. Un patró d'esdeveniment és un JSON amb la mateixa forma que l'esdeveniment, on cada valor és una llista d'alternatives. L'esdeveniment hi encaixa si tots els camps del patró coincideixen.

El patró més simple: totes les comandes confirmades.

{
  "source": ["mercadofresco.tienda"],
  "detail-type": ["PedidoConfirmado"]
}

A partir d'aquí, el llenguatge creix. Aquests són els operadors, aplicats al detall de la comanda:

Operador Patró Què selecciona
Coincidència exacta {"detail":{"franja_entrega":["24h"]}} Franja urgent
Llista (OR) {"detail":{"provincia":["Barcelona","Girona"]}} Qualsevol de les dues
prefix {"detail-type":[{"prefix":"Pedido"}]} PedidoConfirmado i PedidoCancelado
suffix {"detail":{"lineas":{"sku":[{"suffix":"-BIO"}]}}} Alguna línia ecològica
anything-but {"detail":{"provincia":[{"anything-but":["Baleares","Canarias"]}]}} Tot menys les illes
numeric {"detail":{"importe_eur":[{"numeric":[">=",150]}]}} Comandes grans
Rang {"detail":{"importe_eur":[{"numeric":[">",50,"<=",200]}]}} Entre 50 i 200 €
exists {"detail":{"cupon":[{"exists":true}]}} Només amb cupó
cidr {"detail":{"ip":[{"cidr":"10.0.0.0/16"}]}} Origen intern
equals-ignore-case {"detail":{"provincia":[{"equals-ignore-case":"barcelona"}]}} Sense importar majúscules
$or Vegeu a sota OR entre camps diferents

Les regles de composició són les mateixes que a SNS i continuen sent la font número u de patrons que no encaixen: dins d'un camp, la llista és OR; entre camps diferents, AND. Aquest patró exigeix les dues coses alhora:

{
  "source": ["mercadofresco.tienda"],
  "detail-type": ["PedidoConfirmado"],
  "detail": {
    "franja_entrega": ["24h"],
    "importe_eur": [{ "numeric": [">=", 60] }]
  }
}

I aquest selecciona urgents o grans, que és diferent:

{
  "source": ["mercadofresco.tienda"],
  "detail-type": ["PedidoConfirmado"],
  "$or": [
    { "detail": { "franja_entrega": ["24h"] } },
    { "detail": { "importe_eur": [{ "numeric": [">=", 60] }] } }
  ]
}

Els arrays tenen una semàntica pròpia que cal entendre. El patró {"detail":{"lineas":{"sku":[{"prefix":"PESC-"}]}}} encaixa si alguna de les línies comença per PESC-. No hi ha manera d'exigir que totes ho facin: EventBridge avalua els arrays amb semàntica existencial. Si necessites «totes», el filtre va a la destinació, no a la regla.

Crear la regla i enganxar-hi destinacions:

aws events put-rule --name regla-mf-pedido-confirmado-almacen \
  --event-bus-name bus-mercadofresco --state ENABLED \
  --description "Encamina comandes confirmades cap a la cua del magatzem" \
  --event-pattern '{"source":["mercadofresco.tienda"],"detail-type":["PedidoConfirmado"]}' $PERFIL

aws events put-targets --rule regla-mf-pedido-confirmado-almacen \
  --event-bus-name bus-mercadofresco \
  --targets '[{
    "Id": "cola-almacen",
    "Arn": "arn:aws:sqs:eu-west-1:111122223333:cola-mercadofresco-almacen",
    "DeadLetterConfig": {"Arn":"arn:aws:sqs:eu-west-1:111122223333:mercadofresco-eventbridge-fallidas"},
    "RetryPolicy": {"MaximumRetryAttempts": 8, "MaximumEventAgeInSeconds": 3600}
  }]' $PERFIL

Provar un patró sense publicar res és possible i convé fer-ho sempre:

aws events test-event-pattern \
  --event-pattern file://patron.json \
  --event file://evento-ejemplo.json $PERFIL
# → { "Result": true }

Destinacions i transformació de l'entrada

Una regla admet fins a cinc destinacions, de més de 20 tipus. Les que fa servir MercadoFresco:

Destinació Ús Nota
Cua SQS Feina asíncrona duradora La més habitual; permet MessageGroupId en FIFO
Funció Lambda Reacció immediata EventBridge gestiona el permís lambda:InvokeFunction
Tema SNS Reaprofitar el fan-out ja muntat Uneix els dos mons
Màquina d'estats Arrencar el procés de comanda Ho veurem a 07-04
Destinació d'API Cridar l'ERP del soci per HTTPS Amb autenticació i contrapressió gestionades
Un altre bus / un altre compte Federar entorns Requereix política de bus a la destinació
Firehose, Kinesis, Log group Bolcat i auditoria A mercadofresco-registros-web

La destinació d'API mereix atenció perquè resol un problema real. Cridar una API externa des d'una Lambda obliga a gestionar credencials, reintents i límits de ritme a mà. Una destinació d'API ho fa EventBridge: desa les credencials en una connexió (que al seu torn les diposita a Secrets Manager), afegeix la capçalera d'autenticació i limita les invocacions per segon.

aws events create-connection --name conexion-erp-almacen \
  --authorization-type API_KEY \
  --auth-parameters '{"ApiKeyAuthParameters":{"ApiKeyName":"x-api-key","ApiKeyValue":"FICTICIA-123"}}' \
  $PERFIL

aws events create-api-destination --name destino-api-erp-almacen \
  --connection-arn arn:aws:events:eu-west-1:111122223333:connection/conexion-erp-almacen/abc123 \
  --invocation-endpoint https://erp.socio.example/v1/pedidos \
  --http-method POST \
  --invocation-rate-limit-per-second 12 $PERFIL

--invocation-rate-limit-per-second 12 és exactament el límit que l'ERP aguanta. EventBridge no el supera encara que arribin 900 esdeveniments de cop, i la resta espera amb reintents.

La transformació de l'entrada (input transformer) evita l'acoblament entre el format de l'esdeveniment i el que la destinació espera. Sense ella, l'ERP hauria d'entendre el sobre complet d'EventBridge i el nom exacte dels nostres camps, i qualsevol canvi a l'esdeveniment trencaria el soci.

{
  "InputPathsMap": {
    "pedido": "$.detail.pedido_id",
    "cliente": "$.detail.cliente_id",
    "franja": "$.detail.franja_entrega",
    "momento": "$.time"
  },
  "InputTemplate": "{\"orderRef\":\"<pedido>\",\"customerRef\":\"<cliente>\",\"deliverySlot\":\"<franja>\",\"createdAt\":\"<momento>\",\"channel\":\"web\"}"
}

InputPathsMap extreu valors amb JSONPath i els dona un àlies; InputTemplate compon la càrrega que rep la destinació. El resultat és que l'ERP rep el seu propi vocabulari (orderRef, customerRef) sense que MercadoFresco hagi de reanomenar res al seu esdeveniment. La traducció viu a la regla, que és el lloc barat de canviar.

Reintents, edat màxima de l'esdeveniment i DLQ per destinació

Si una destinació falla, EventBridge reintenta amb retrocés exponencial durant 24 hores i fins a 185 intents per defecte. Tots dos límits s'ajusten per destinació, i el primer que es compleixi atura els intents:

  • MaximumRetryAttempts: nombre màxim de reintents (0–185).
  • MaximumEventAgeInSeconds: antiguitat màxima de l'esdeveniment (60–86.400 s).

MaximumEventAgeInSeconds és el paràmetre que més s'oblida i el que més importa en negoci. Un avís de repartiment lliurat 20 hores tard no val res: és preferible que caigui a la DLQ al cap d'una hora i que algú se'l miri, en lloc que arribi quan el repartiment ja s'ha fet. La Marta el fixa en 3.600 s per a tot el que té a veure amb comandes.

La DLQ per destinació (DeadLetterConfig) recull el que no s'ha pogut lliurar. És una cua SQS normal, i el missatge que hi arriba inclou atributs amb el motiu: RULE_ARN, TARGET_ARN, ERROR_CODE, ERROR_MESSAGE i EXHAUSTED_RETRY_CONDITION. Aquest últim camp és or pur per al diagnòstic: diu si es van esgotar els reintents o si va vèncer l'edat màxima.

No confonguis les tres DLQ que ja conviuen a MercadoFresco:

DLQ Recull Causa típica
De la cua SQS El que el consumidor no va poder processar Dades invàlides, dependència caiguda
De la subscripció SNS El que SNS no va poder lliurar Permisos, endpoint caigut
De la destinació d'EventBridge El que EventBridge no va poder lliurar Permisos, destinació saturada, edat vençuda

Esdeveniments dels serveis d'AWS

Aquesta és la capacitat que SNS no té: més de 200 serveis d'AWS publiquen al bus default sense que configuris res. Només cal escriure la regla.

Una instància del grup de la botiga que s'apaga o passa a estat degradat:

{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": { "state": ["stopped", "terminated", "stopping"] }
}

Una troballa de Trusted Advisor (05-05), que fins ara calia anar a mirar a mà:

{
  "source": ["aws.trustedadvisor"],
  "detail-type": ["Trusted Advisor Check Item Refresh Notification"],
  "detail": { "status": ["WARN", "ERROR"] }
}

Una tasca d'ECS que mor (mòdul 10) o un desplegament de CodeDeploy que falla (08-03):

{
  "source": ["aws.ecs", "aws.codedeploy"],
  "detail-type": [
    "ECS Task State Change",
    "CodeDeploy Deployment State-change Notification"
  ],
  "detail": { "state": ["STOPPED"], "status": ["FAILURE"] }
}

Compte amb aquest últim: en combinar dos orígens en una regla, el patró detail s'aplica a tots dos i pot no encaixar amb cap. És més net una regla per origen, encara que sembli repetitiu: els patrons queden llegibles i cadascuna pot tenir les seves pròpies destinacions i la seva pròpia DLQ.

MercadoFresco crea regla-mf-infra-incidentes amb destinació alertas-mercadofresco, i la transformació de l'entrada converteix l'esdeveniment cru en una frase llegible per a la Marta:

{
  "InputPathsMap": { "recurs": "$.detail.instance-id", "estat": "$.detail.state" },
  "InputTemplate": "\"La instancia <recurs> ha passat a estat <estat> a eu-west-1.\""
}

Aquest és un bon moment per assenyalar el canvi de mentalitat: l'operació passa de consultar a reaccionar. En lloc que algú revisi Trusted Advisor cada dilluns, un esdeveniment obre un tiquet quan hi ha alguna cosa per mirar.

Regles programades i EventBridge Scheduler

EventBridge també dispara per temps. Hi ha dos mecanismes i convé saber quin fer servir.

Les regles programades clàssiques viuen al bus default i fan servir schedule-expression:

aws events put-rule --name regla-mf-limpiar-carritos \
  --schedule-expression "cron(0 3 * * ? *)" --state ENABLED \
  --description "Neteja diaria de cistelles caducades a les 03:00 UTC" $PERFIL

EventBridge Scheduler és el servei dedicat, més nou i preferible per a gairebé tot: admet zones horàries amb horari d'estiu, finestres de dispersió (flexible time windows) per no llançar mil tasques al mateix segon, programacions d'un sol ús, i més de 270 destinacions amb els mateixos reintents i DLQ que les regles.

aws scheduler create-schedule-group --name grupo-mf-programado $PERFIL

aws scheduler create-schedule --name programador-mf-carga-nocturna \
  --group-name grupo-mf-programado \
  --schedule-expression "cron(30 2 * * ? *)" \
  --schedule-expression-timezone "Europe/Madrid" \
  --flexible-time-window '{"Mode":"FLEXIBLE","MaximumWindowInMinutes":15}' \
  --target '{
    "Arn": "arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-carga-redshift",
    "RoleArn": "arn:aws:iam::111122223333:role/rol-mf-scheduler",
    "Input": "{\"origen\":\"aurora\",\"destino\":\"analitica.hechos_pedidos\",\"modo\":\"incremental\"}",
    "RetryPolicy": {"MaximumRetryAttempts": 3, "MaximumEventAgeInSeconds": 3600},
    "DeadLetterConfig": {"Arn":"arn:aws:sqs:eu-west-1:111122223333:mercadofresco-eventbridge-fallidas"}
  }' $PERFIL

El --schedule-expression-timezone "Europe/Madrid" és la raó principal per preferir Scheduler: amb una regla clàssica en UTC, la càrrega nocturna s'executaria a les 04:30 a l'hivern i a les 03:30 a l'estiu, desalineada amb el tancament comptable. I MaximumWindowInMinutes: 15 reparteix l'arrencada per no crear un pic de connexions contra aurora-mf-lector-2.

Regla programada EventBridge Scheduler
Zona horària i horari d'estiu No (només UTC)
Finestra flexible No
Programació d'un sol ús No Sí (at(...))
Límit per compte 300 regles per bus 1 milió de programacions
Destinacions ~20 tipus 270+
Cost Gratis 1,00 USD/milió d'invocacions

Les dues tasques nocturnes de MercadoFresco queden així: programador-mf-carga-nocturna a les 02:30 hora peninsular carrega les comandes del dia a analitica.hechos_pedidos de wg-mercadofresco-analitica, i programador-mf-limpiar-carritos a les 03:00 recorre i consolida el que el TTL de mercadofresco-carritos ja ha anat caducant. Compte: les expressions cron d'AWS tenen sis camps (minut, hora, dia del mes, mes, dia de la setmana, any) i no admeten * a dia del mes i dia de la setmana alhora —d'aquí el ?—.

Registre d'esquemes i enllaços de codi

El registre d'esquemes guarda l'estructura dels teus esdeveniments en format OpenAPI/JSON Schema. Amb el descobriment activat en un bus, EventBridge infereix l'esquema del que hi passa i el versiona automàticament.

aws schemas create-discoverer \
  --source-arn arn:aws:events:eu-west-1:111122223333:event-bus/bus-mercadofresco $PERFIL
aws schemas list-schemas --registry-name discovered-schemas $PERFIL

D'un esquema es generen enllaços de codi (code bindings) per a Python, Java o TypeScript: classes tipades que representen l'esdeveniment, de manera que el consumidor deixa de fer evento["detail"]["pedido_id"] amb els dits creuats. En Python el valor afegit és menor que en Java, però el registre continua sent útil per un altre motiu: és la documentació viva del catàleg. Quan màrqueting pregunti quins camps porta PedidoConfirmado, la resposta és un esquema versionat, no un missatge de xat. El descobriment costa uns 0,10 USD per milió d'esdeveniments processats, així que convé tenir-lo actiu només mentre el catàleg evoluciona.

Arxiu i reproducció d'esdeveniments

Aquesta és la capacitat que hauria salvat l'incident de 07-02. Un arxiu desa còpia de tots els esdeveniments que passen per un bus —opcionalment filtrats per patró— durant el temps que decideixis.

aws events create-archive --archive-name archivo-mercadofresco-eventos \
  --event-source-arn arn:aws:events:eu-west-1:111122223333:event-bus/bus-mercadofresco \
  --retention-days 90 \
  --event-pattern '{"source":[{"prefix":"mercadofresco."}]}' $PERFIL

I quan alguna cosa va malament, es reprodueix una finestra de temps cap a les regles afectades:

aws events start-replay --replay-name reproduccion-analitica-20260802 \
  --event-source-arn arn:aws:events:eu-west-1:111122223333:archive/archivo-mercadofresco-eventos \
  --event-start-time 2026-08-02T08:00:00Z \
  --event-end-time 2026-08-02T14:00:00Z \
  --destination '{
    "Arn": "arn:aws:events:eu-west-1:111122223333:event-bus/bus-mercadofresco",
    "FilterArns": ["arn:aws:events:eu-west-1:111122223333:rule/bus-mercadofresco/regla-mf-pedido-confirmado-analitica"]
  }' $PERFIL

FilterArns és imprescindible i és el que separa una recuperació d'un desastre: sense ell, la reproducció dispara totes les regles del bus, i tornaries a cobrar targetes, a enviar correus i a avisar el magatzem de comandes de fa sis hores. Amb ell, només es reprocessa la regla d'analítica, que és la que es va perdre els esdeveniments.

Tres coses més que cal saber abans de fer-ho servir de debò:

  • Els esdeveniments reproduïts porten replay-name al sobre. Un consumidor els pot distingir i actuar en conseqüència —per exemple, no tornar a notificar ningú—.
  • L'ordre no es garanteix dins de la reproducció, i els esdeveniments arriben molt més de pressa que al seu moment original. El consumidor ha de ser idempotent i aguantar el ritme.
  • Reproduir costa com publicar. Un mes d'esdeveniments reproduït són un mes d'esdeveniments facturats de nou, més l'emmagatzematge de l'arxiu (uns 0,10 USD per GB i mes).

L'arxiu també serveix per a una cosa menys dramàtica i molt útil: poblar un entorn de proves amb trànsit real, reproduint una tarda de divendres contra un bus de desenvolupament.

EventBridge Pipes i els Streams de mercadofresco-carritos

A 06-02 vam activar DynamoDB Streams a mercadofresco-carritos i ho vam deixar allà, presentat i sense explotar. EventBridge Pipes és la peça que faltava: una canonada punt a punt entre un origen que se sondeja i una destinació, amb dos passos opcionals al mig.

flowchart LR
    ORIG[(DynamoDB Streams<br/>mercadofresco-carritos)] --> FIL[Filtratge<br/>nomes REMOVE per TTL]
    FIL --> ENR[Enriquiment<br/>Lambda: afegeix dades del client]
    ENR --> DEST{{bus-mercadofresco<br/>CarritoAbandonado}}
    DEST --> R1[regla: avis a marqueting]
    DEST --> R2[regla: cua d'analitica]
    style FIL fill:#fff3cd
    style ENR fill:#cfe2ff

Els orígens de Pipes són els que cal sondejar: DynamoDB Streams, Kinesis, SQS, Amazon MQ i Kafka. Les destinacions són qualsevol d'EventBridge. I els dos passos intermedis són la raó de la seva existència:

  • Filtratge: descarta el que no interessa abans de pagar per processar-ho. El stream de cistelles genera un registre per cada UpdateItem —centenars de milers al dia—, però només interessen els REMOVE provocats pel TTL, que són les cistelles abandonades de debò.
  • Enriquiment: crida una Lambda, una API o una màquina d'estats per completar el registre abans de lliurar-lo. El stream porta el CLIENTE#<id> però no el correu ni el nom; l'enriquiment els busca a Aurora.
aws pipes create-pipe --name pipe-mf-carritos-abandonados \
  --role-arn arn:aws:iam::111122223333:role/rol-mf-pipes \
  --source arn:aws:dynamodb:eu-west-1:111122223333:table/mercadofresco-carritos/stream/2026-07-01T00:00:00.000 \
  --source-parameters '{
    "DynamoDBStreamParameters": {"StartingPosition":"LATEST","BatchSize":50,
                                 "MaximumBatchingWindowInSeconds":30},
    "FilterCriteria": {"Filters":[{"Pattern":"{\"eventName\":[\"REMOVE\"],\"userIdentity\":{\"principalId\":[\"dynamodb.amazonaws.com\"]}}"}]}
  }' \
  --enrichment arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-enriquecer-carrito \
  --target arn:aws:events:eu-west-1:111122223333:event-bus/bus-mercadofresco \
  --target-parameters '{"EventBridgeEventBusParameters":{"Source":"mercadofresco.carritos",
                        "DetailType":"CarritoAbandonado"}}' $PERFIL

El filtre té un detall exquisit: userIdentity.principalId = dynamodb.amazonaws.com distingeix els esborrats fets pel TTL dels que fa l'aplicació en confirmar una comanda. Sense aquesta condició, màrqueting enviaria correus de «t'has deixat alguna cosa» a clients que acaben de comprar. És un exemple perfecte de per què el filtratge ha d'estar a prop de l'origen i entendre el domini.

Amb això es tanca un fil que estava obert des del mòdul 6: els 55 GB de cistelles zombis que el TTL va començar a netejar ara, a més, generen un esdeveniment de negoci aprofitable.

Arquitectura dirigida per esdeveniments: contractes i versionatge

Els esdeveniments desacoblen, però no de franc. Hi ha tres veritats que convé acceptar aviat.

Un esdeveniment és una API pública. Tan bon punt el publiques en un bus compartit, no saps qui el consumeix: aquest és l'objectiu. I llavors no el pots canviar al teu gust. Treure un camp, reanomenar-lo o canviar-ne el tipus trenca consumidors que ni tan sols saps que existeixen, i ho descobriràs en producció.

Les regles d'evolució compatible són curtes: es poden afegir camps opcionals; no es pot treure ni reanomenar un camp, ni canviar-ne el tipus, ni canviar el significat d'un valor existent, ni endurir una restricció. Quan calgui un canvi incompatible, es publica una versió nova en paral·lel —"version": 2 al detall, o un detail-type nou PedidoConfirmadoV2— i es mantenen totes dues fins que els consumidors migrin. Per això version va al detall des del primer esdeveniment: sense ell, la migració no té per on començar.

L'acoblament no desapareix, es desplaça. Abans la botiga depenia de l'ERP; ara tothom depèn del format de l'esdeveniment. És un canvi molt favorable —el contracte és explícit, versionable i verificable amb test-event-pattern— però continua sent una dependència. La disciplina que el manté sa és la del catàleg: res no es publica a bus-mercadofresco sense ser a la taula, i res no es canvia sense pujar la versió.

Un últim advertiment sobre el disseny: els esdeveniments descriuen fets, les ordres manen accions. Si el teu esdeveniment es diu EnviarCorreoConfirmacion, has posat una ordre en un bus, i l'emissor torna a saber què ha de passar després. L'esdeveniment correcte és PedidoConfirmado; que això impliqui un correu és decisió del consumidor.

Observabilitat i cost

Mètrica Què indica Acció
TriggeredRules Regles que hi van encaixar Si és 0, el patró no coincideix
MatchedEvents Esdeveniments que van encaixar amb alguna regla Comparar amb el publicat
FailedInvocations La destinació va rebutjar la invocació Alarma: gairebé sempre permisos
InvocationsFailedToBeSentToDlq Ni tan sols es va poder escriure a la DLQ Alarma crítica
ThrottledRules S'ha superat el límit d'invocacions Demanar augment de quota
PutEventsFailedEntriesCount Entrades rebutjades en publicar Revisar el publicador

La combinació més útil per depurar és MatchedEvents = 0 amb publicacions no nul·les: significa que cap patró no hi encaixa, i gairebé sempre és un source mal escrit o un detail-type amb una majúscula de diferència —els patrons distingeixen majúscules—.

A més, EventBridge s'integra amb CloudWatch Logs com a destinació: una regla amb patró ampli i com a destinació un grup de registres permet veure tot el que passa pel bus durant una investigació. És car deixar-ho permanentment, però és l'eina més ràpida quan alguna cosa no apareix on hauria.

Concepte Preu MercadoFresco
Esdeveniments personalitzats publicats 1,00 USD/milió 480.000/mes → 0,48 USD
Esdeveniments de serveis d'AWS Gratis ~90.000/mes → 0 USD
Regles i destinacions Gratis
Arxiu (emmagatzematge) ~0,10 USD/GB-mes 3,4 GB × 90 dies → 0,34 USD
Reproducció 1,00 USD/milió Només en incidents
Descobriment d'esquemes ~0,10 USD/milió 0,05 USD
Pipes ~0,40 USD/milió de peticions 210.000 després de filtrar → 0,08 USD
Scheduler 1,00 USD/milió 60/mes → 0 USD
Total ~0,95 USD/mes

Els esdeveniments no filtrats són el que pot disparar la factura: si el pipe de cistelles no filtrés els MODIFY, processaria 5,4 milions de registres al mes en lloc de 210.000. Filtrar a l'origen no és només higiene de disseny; és la partida principal.

aws events remove-targets --rule regla-mf-pedido-confirmado-almacen \
  --event-bus-name bus-mercadofresco --ids cola-almacen $PERFIL
aws events delete-rule --name regla-mf-pedido-confirmado-almacen \
  --event-bus-name bus-mercadofresco $PERFIL
aws pipes delete-pipe --name pipe-mf-carritos-abandonados $PERFIL
aws scheduler delete-schedule --name programador-mf-carga-nocturna \
  --group-name grupo-mf-programado $PERFIL
aws events delete-archive --archive-name archivo-mercadofresco-eventos $PERFIL
aws events delete-event-bus --name bus-mercadofresco $PERFIL

Un bus no es pot esborrar si té regles amb destinacions: cal treure destinacions, després regles, després el bus. És la causa habitual de ResourceInUseException als scripts de neteja.

Errors Habituals i Consells

Patró que no encaixa per majúscules o per un source mal escrit. MatchedEvents a 0 sense cap error. Fes servir test-event-pattern abans de desplegar; t'estalvia hores. I no confonguis OR i AND: dins d'un camp, llista = OR; entre camps, AND; per a OR entre camps, $or.

Esperar que un patró sobre un array exigeixi «tots». EventBridge avalua els arrays amb semàntica existencial: encaixa si algun dels elements ho compleix.

No comprovar FailedEntryCount a put_events. Retorna 200 amb fallades parcials, igual que send_message_batch. Esdeveniments perduts en silenci.

Publicar els esdeveniments de negoci al bus default. Es barregen amb el soroll d'AWS, les regles es tornen fràgils i l'arxiu s'omple d'esdeveniments que no interessen. Fes servir un bus propi.

Deixar l'edat màxima de l'esdeveniment en 24 hores. Un avís de repartiment lliurat 20 hores tard és pitjor que una fallada visible. Ajusta MaximumEventAgeInSeconds al valor de negoci.

Reproduir sense FilterArns. Dispara totes les regles del bus i repeteix efectes que ja van passar. És l'error més car d'aquesta lliçó.

Oblidar la DLQ de la destinació. Sense ella, el que no es lliura desapareix sense deixar rastre.

Posar ordres al bus. Si el detail-type és un verb en imperatiu, has reintroduït l'acoblament que volies eliminar.

Canviar el detail sense pujar version. Trenques consumidors que no saps que existeixen i te n'assabentes en producció.

Consell: una regla per intenció, no una regla amb cinc destinacions heterogènies. Les regles separades tenen la seva pròpia DLQ, la seva pròpia edat màxima i la seva pròpia mètrica, i es poden desactivar d'una en una durant un incident.

Consell: escriu el catàleg abans que el codi. La taula de source / detail-type / camps són deu minuts de feina que eviten mesos d'esdeveniments incoherents.

Consell: activa l'arxiu des del primer dia. Costa cèntims i és l'única manera de recuperar el que un consumidor mal configurat es va perdre.

Exercicis

Exercici 1: encaminar el catàleg complet

Dissenya les regles de bus-mercadofresco per a aquests quatre requisits: (1) tots els PedidoConfirmado van a cola-mercadofresco-almacen; (2) els PedidoConfirmado de franja 24h o d'import ≥ 100 € van a més a la destinació d'API de l'ERP amb prioritat; (3) els StockBajo de productes frescos (SKU que comencen per FRUT- o PESC-) amb menys de 5 unitats avisen alertas-mercadofresco; (4) qualsevol esdeveniment de mercadofresco.* s'arxiva 90 dies.

Escriu els patrons JSON, digues quantes regles crees i per què, i quina transformació de l'entrada faries servir al requisit 3.

Exercici 2: el consumidor que es va perdre una tarda

El dimarts a les 09:00 el Luis desplega una versió de la Lambda d'analítica que llança una excepció en arrencar. Ningú no ho detecta fins a les 15:00. La regla regla-mf-pedido-confirmado-analitica lliura directament a aquella Lambda, sense cua. Hi ha arxiu actiu des de fa tres mesos.

Respon: (a) què ha passat amb els esdeveniments d'aquelles sis hores segons la configuració de reintents per defecte i segons una edat màxima de 3.600 s; (b) com recuperes les dades, amb l'ordre exacta; (c) quina precaució prens abans de llançar-la; (d) quins dos canvis d'arquitectura eviten que torni a passar; (e) quina alarma hauria avisat a les 09:05.

Exercici 3: SNS, EventBridge o Pipes

Decideix el servei i justifica-ho en dues frases: (a) tres equips volen les comandes confirmades, amb filtres simples i la mínima latència possible; (b) cal cridar l'ERP del soci per HTTPS amb clau d'API i com a màxim 12 peticions per segon; (c) cal avisar per SMS el tècnic de guàrdia; (d) cal llançar la càrrega a Redshift cada dia a les 02:30 hora peninsular; (e) cal reaccionar als esborrats per TTL de mercadofresco-carritos enriquint amb dades d'Aurora; (f) cal reaccionar que una instància EC2 passi a terminated.

Solucions

Solució 1

Quatre regles més un arxiu. Se separen perquè cadascuna té destinació, edat màxima i DLQ propis, i perquè una regla amb patrons barrejats es torna il·legible.

(1) regla-mf-pedido-confirmado-almacen:

{ "source": ["mercadofresco.tienda"], "detail-type": ["PedidoConfirmado"] }

(2) regla-mf-pedido-prioritario-erp. L'OR entre camps diferents obliga a $or:

{
  "source": ["mercadofresco.tienda"],
  "detail-type": ["PedidoConfirmado"],
  "$or": [
    { "detail": { "franja_entrega": ["24h"] } },
    { "detail": { "importe_eur": [{ "numeric": [">=", 100] }] } }
  ]
}

Destinació destino-api-erp-almacen amb MaximumEventAgeInSeconds baix (900 s: una comanda urgent que no arriba en 15 minuts s'ha de tractar a mà) i DLQ mercadofresco-eventbridge-fallidas.

(3) regla-mf-stock-bajo-fresco. Aquí sí que és AND entre camps: prefix de SKU i unitats.

{
  "source": ["mercadofresco.almacen"],
  "detail-type": ["StockBajo"],
  "detail": {
    "sku": [{ "prefix": "FRUT-" }, { "prefix": "PESC-" }],
    "unidades_restantes": [{ "numeric": ["<", 5] }]
  }
}

Els dos prefix dins de la llista de sku són un OR, que és just el que es demana. Transformació de l'entrada, perquè la destinació és un tema d'SNS que acaba al correu d'una persona:

{
  "InputPathsMap": { "sku": "$.detail.sku", "queden": "$.detail.unidades_restantes" },
  "InputTemplate": "\"Estoc critic: queden <queden> unitats de <sku>. Reposar avui.\""
}

(4) No és una regla, és un arxiu amb patró {"source":[{"prefix":"mercadofresco."}]} i --retention-days 90. Convé el prefix per no arxivar esdeveniments d'AWS que no aporten i sí que ocupen.

Solució 2

(a) Amb els valors per defecte (185 reintents, 24 hores), els esdeveniments de les 09:00 encara s'estarien reintentant a les 15:00, i en arreglar la Lambda una part es lliuraria sola, tot i que amb hores de retard i desordenada. Amb MaximumEventAgeInSeconds=3600, cada esdeveniment es descarta al cap d'una hora de publicar-se: a les 15:00 tot el que és anterior a les 14:00 és a la DLQ de la destinació i la resta continua reintentant. La segona configuració és preferible: converteix una degradació silenciosa en un munt visible de missatges a la DLQ.

(b) Primer s'arregla la Lambda i es comprova amb un esdeveniment de prova. Després:

aws events start-replay --replay-name reproduccion-analitica-20260804 \
  --event-source-arn arn:aws:events:eu-west-1:111122223333:archive/archivo-mercadofresco-eventos \
  --event-start-time 2026-08-04T07:00:00Z --event-end-time 2026-08-04T13:00:00Z \
  --destination '{"Arn":"arn:aws:events:eu-west-1:111122223333:event-bus/bus-mercadofresco",
                  "FilterArns":["arn:aws:events:eu-west-1:111122223333:rule/bus-mercadofresco/regla-mf-pedido-confirmado-analitica"]}'

Les hores van en UTC: les 09:00–15:00 peninsulars d'agost són 07:00–13:00 UTC. Equivocar-s'hi és un clàssic.

(c) Tres precaucions. FilterArns obligatori, o la reproducció dispararia totes les regles i tornaria a avisar el magatzem i a facturar comandes de fa sis hores. Verificar que el consumidor és idempotent, perquè part dels esdeveniments es va poder lliurar abans de fallar i la reproducció els repetirà. I buidar o revisar abans la DLQ de la destinació, per no reprocessar dos cops per dos camins.

(d) Primer, posar una cua entre la regla i la Lambda: amb SQS, sis hores de fallada deixen 5.000 missatges esperant i es processen en arreglar el problema sense reproduir res; és la mateixa lliçó de 07-02 aplicada a EventBridge. Segon, DLQ a la destinació més alarma, perquè el senyal aparegui en minuts. Com a tercera mesura, un desplegament amb verificació de salut que reverteixi sol, que és justament el tema del mòdul 8.

(e) Una alarma sobre FailedInvocations de la regla, llindar > 0 en un període de 5 minuts, cap a alertas-mercadofresco. Complementària: Errors de la funció Lambda. Qualsevol de les dues hauria avisat a les 09:05 en lloc de a les 15:00.

Solució 3

(a) SNS. Fan-out pur, filtres simples i requisit de latència mínima: SNS lliura en desenes de mil·lisegons davant dels centenars d'EventBridge, i ja està muntat. Cada equip amb la seva cua subscrita.

(b) EventBridge amb destinació d'API. És exactament el seu cas d'ús: gestiona la clau en una connexió recolzada per Secrets Manager, aplica invocation-rate-limit-per-second 12 i reintenta amb DLQ, tot sense escriure una línia de codi.

(c) SNS. És l'únic dels tres que lliura SMS. A més, l'alarma de CloudWatch ja hi publica de manera nativa.

(d) EventBridge Scheduler. Necessita zona horària amb horari d'estiu (Europe/Madrid), que les regles programades clàssiques no admeten, i la finestra flexible evita el pic de connexions contra el lector d'Aurora.

(e) EventBridge Pipes. És l'únic que consumeix DynamoDB Streams amb filtratge abans de facturar i amb un pas d'enriquiment integrat. Fer-ho amb una Lambda subscrita al stream obligaria a filtrar i enriquir a mà, processant —i pagant— els milions de MODIFY que no interessen.

(f) EventBridge, bus default. Els esdeveniments d'EC2 arriben sols i són gratuïts; no hi ha res per publicar. Una regla amb patró sobre aws.ec2 i destinació alertas-mercadofresco.

Conclusió

MercadoFresco ja no té un tema per tipus de fet, sinó un bus on conviuen PedidoConfirmado, PedidoCancelado, StockBajo i RepartoAsignado, i on regles amb patrons JSON decideixen on va cadascun mirant-ne el contingut: la franja, l'import, la província, el prefix del SKU. L'ERP del soci rep el seu propi vocabulari gràcies a la transformació de l'entrada, sense que ningú reanomeni res a l'esdeveniment. Les tasques nocturnes —la càrrega a analitica.hechos_pedidos i la consolidació de cistelles— corren amb programador-mf-carga-nocturna en hora peninsular i amb finestra flexible. Els incidents d'infraestructura i les troballes de Trusted Advisor arriben sols, sense que ningú hagi d'anar a mirar. I els Streams de mercadofresco-carritos, presentats a 06-02 i sense fer servir fins avui, alimenten per fi un esdeveniment de negoci a través de pipe-mf-carritos-abandonados, amb un filtre que distingeix l'esborrat del TTL de l'esborrat per compra.

Dues idees s'emporten el pes de la lliçó. La primera és que l'encaminament per contingut canvia qui decideix: ja no és l'emissor qui reparteix la feina, sinó una regla declarativa que es modifica sense desplegar codi. La segona és que un esdeveniment és una API pública, amb tot el que això implica: es pot afegir, no es pot treure, i sense version al detall no hi ha migració possible. Entremig queda archivo-mercadofresco-eventos, la xarxa de seguretat que converteix «hem perdut sis hores de dades» en una reproducció d'una sola ordre —sempre amb FilterArns, o el remei serà pitjor que la malaltia—.

Però fixa't en el que continua sense resoldre. Tot el que hem muntat és coreografia: cada component reacciona al que veu i ningú no té la foto completa. Funciona de meravella per a fets independents, i es trenca quan el procés té passos que depenen els uns dels altres i cal desfer el que s'ha fet si alguna cosa falla a mitges. La comanda de MercadoFresco és exactament això: cobrar, reservar estoc, demanar la preparació al magatzem —que triga entre dos minuts i una hora i la confirma una persona amb un lector de codis—, assignar repartiment i confirmar al client. Si el magatzem no pot preparar la caixa perquè el peix ha arribat en mal estat, cal tornar el cobrament i alliberar l'estoc, i amb esdeveniments solts ningú no sap en quin punt era el procés ni què toca compensar.

A 07-04, «AWS Step Functions», passem de la coreografia a l'orquestració: una màquina d'estats, mercadofresco-procesar-pedido, que coneix el procés sencer, desa en quin pas va, reintenta amb retrocés exponencial, espera que un humà confirmi mitjançant un testimoni de retorn, processa les 900 confirmacions del divendres en paral·lel amb Map distribuït, i —el més important— sap desfer el que ja havia fet quan alguna cosa surt malament.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats