Tot el que hem muntat al mòdul és coreografia: cada component reacciona al que veu i ningú no té la foto completa. Funciona esplèndidament per a fets independents —enviar un correu, carregar analítica, generar una miniatura— i es trenca tan bon punt els passos depenen els uns dels altres i cal desfer el que s'ha fet si alguna cosa falla a mig camí.
El procés de comanda de MercadoFresco és exactament aquest cas: cobrar la targeta, reservar l'estoc, demanar al magatzem que prepari la caixa —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 peix arriba en mal estat i el magatzem no pot preparar la comanda, cal tornar el cobrament i alliberar l'estoc. Amb esdeveniments solts ningú no sap en quin punt era el procés ni què toca compensar; l'única manera és una taula d'estat escrita a mà, un procés que la vigili i un munt de casos rars que no es proven mai.
AWS Step Functions és l'orquestrador sense servidor d'AWS. Defineixes el procés com una màquina
d'estats en JSON, i el servei s'encarrega d'executar cada pas, desar l'estat entre passos, reintentar
amb retrocés exponencial, capturar errors, esperar hores o dies si cal, i deixar un historial complet de
cada execució. Aquí el Luis construeix mercadofresco-procesar-pedido amb el seu camí de compensació
complet.
Avís de cost. Els fluxos estàndard costen 0,025 USD per 1.000 transicions d'estat; els exprés, per nombre d'execucions i per GB-segon. Un bucle mal dissenyat amb
Waitcurt pot generar centenars de milers de transicions en una nit. Al final tens la neteja. Dades fictícies.
Contingut
- Coreografia enfront d'orquestració
- Fluxos estàndard i fluxos exprés
- Amazon States Language: l'estructura
- Els vuit tipus d'estat
- Flux de dades entre estats
- Integracions i patrons de servei
- El testimoni de retorn per al pas humà del magatzem
- Gestió d'errors:
RetryiCatch - El patró saga: compensar el que ja s'ha fet
mercadofresco-procesar-pedidocompletaMapdistribuït per al volum del divendres- Desplegament i arrencada des d'EventBridge
- Observabilitat: depurar una execució fallida
- Workflow Studio i cost
- Errors habituals i consells
- Exercicis
- Conclusió
Coreografia enfront d'orquestració
| Coreografia (esdeveniments) | Orquestració (flux amb estat) | |
|---|---|---|
| Qui coneix el procés | Ningú: està repartit | L'orquestrador, en un sol lloc |
| Acoblament | Mínim | Mitjà: l'orquestrador coneix els passos |
| Afegir un pas | Subscriure un consumidor | Editar la definició |
| Veure en quin punt va | Reconstruir a partir dels registres | Consulta directa |
| Desfer el que s'ha fet | Molt difícil | Catch + compensacions |
| Esperar hores o dies | Necessita desar l'estat | Natiu (Wait, testimoni de retorn) |
| Punt únic de fallada | No | L'orquestrador (gestionat, però conceptual) |
| Escala | Enorme | Alta, amb límits per compte |
Tria coreografia quan els consumidors són independents, l'ordre no importa, cadascun pot fallar pel
seu compte sense afectar els altres i vols poder afegir interessats sense tocar res. És el que hem fet
amb PedidoConfirmado: el correu, l'analítica i màrqueting no es coneixen ni els cal.
Tria orquestració quan hi ha un procés de negoci amb nom, els seus passos depenen del resultat dels anteriors, hi ha decisions condicionals, hi ha esperes llargues o intervenció humana, i —sobretot— cal compensar el que s'ha fet si alguna cosa falla a mig camí. Cobrar una targeta i no poder servir la comanda no s'arregla amb reintents: s'arregla tornant els diners, i algú ha de saber que cal fer-ho.
MercadoFresco acaba amb totes dues: bus-mercadofresco reparteix els fets, i una de les seves regles
arrenca la màquina d'estats que governa el procés de comanda. No competeixen; es complementen.
Fluxos estàndard i fluxos exprés
| Estàndard | Exprés | |
|---|---|---|
| Durada màxima | 1 any | 5 minuts |
| Semàntica d'execució | Exactament una vegada | Almenys una vegada (síncron: com a molt una) |
| Preu | 0,025 USD/1.000 transicions | Per execució + GB-segon |
| Rendiment | 2.000 arrencades/s | 100.000 arrencades/s |
| Historial d'execució | 90 dies a la consola, amb detall | Només a CloudWatch Logs, si l'actives |
Testimoni de retorn (waitForTaskToken) |
Sí | Només en mode síncron |
Integracions .sync |
Sí | No |
| Cas d'ús | Processos de negoci llargs i crítics | Volum alt, curt, idempotent |
La fila decisiva és la semàntica. Un flux estàndard garanteix que cada pas s'executa exactament una vegada: si el servei té un problema intern, reprèn des d'on era sense repetir. Un flux exprés pot reexecutar un pas, així que tots els seus passos han de ser idempotents.
Per a MercadoFresco: mercadofresco-procesar-pedido és estàndard, perquè cobra targetes, dura fins
a una hora i no es pot permetre repetir un cobrament. En canvi, la validació de la cistella abans de
pagar —comprovar estoc, recalcular preus, aplicar cupons, tot en menys d'un segon i sense efectes
externs— és una candidata perfecta per a un flux exprés (mercadofresco-validar-carrito).
Amazon States Language: l'estructura
Una màquina d'estats és un objecte JSON amb tres claus de nivell superior.
{
"Comment": "Proceso de pedido de MercadoFresco",
"StartAt": "CobrarPago",
"TimeoutSeconds": 5400,
"States": {
"CobrarPago": { "Type": "Task", "Resource": "...", "Next": "ReservarStock" },
"ReservarStock": { "Type": "Task", "Resource": "...", "End": true }
}
}StartAt: el nom del primer estat. Obligatori.States: un objecte on cada clau és el nom de l'estat. Els noms són identificadors únics i apareixen tal qual a la consola i a l'historial, així que convé que siguin descriptius i estables: canviar-los trenca els enllaços de les alarmes i confon l'historial.- Cada estat té un
Typei, tret dels terminals, unNexto"End": true.
No hi ha bucles explícits ni variables globals en el sentit clàssic: el «bucle» es fa amb un Choice
que torna a un estat anterior, i l'«estat» és el JSON que viatja d'un estat al següent. Aquesta
restricció és deliberada i fa que el flux sigui inspeccionable.
Els vuit tipus d'estat
Task — fa feina. És l'únic que crida alguna cosa externa.
"CobrarPago": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "mercadofresco-cobrar-pago",
"Payload.$": "$.pedido"
},
"ResultSelector": { "referencia_cobro.$": "$.Payload.referencia" },
"ResultPath": "$.cobro",
"TimeoutSeconds": 30,
"Next": "ReservarStock"
}Choice — bifurca segons el contingut. No fa feina, decideix.
"EsUrgente": {
"Type": "Choice",
"Choices": [
{
"And": [
{ "Variable": "$.pedido.franja_entrega", "StringEquals": "24h" },
{ "Variable": "$.pedido.importe_eur", "NumericGreaterThanEquals": 60 }
],
"Next": "AsignarRepartoPrioritario"
},
{ "Variable": "$.pedido.provincia", "StringMatches": "Baleares", "Next": "RutaInsular" }
],
"Default": "AsignarReparto"
}Els comparadors cobreixen cadenes, nombres, booleans, marques de temps i presència (IsPresent,
IsNull), amb variants Path per comparar dos camps entre si. Default no és opcional a la
pràctica: si cap condició no encaixa i no hi ha Default, l'execució falla amb States.NoChoiceMatched.
Parallel — executa diverses branques alhora amb la mateixa entrada, i retorna un array amb els
resultats en l'ordre de les branques.
"NotificarEnParalelo": {
"Type": "Parallel",
"Branches": [
{ "StartAt": "AvisarCliente", "States": { "AvisarCliente": {
"Type": "Task", "Resource": "arn:aws:states:::sns:publish",
"Parameters": {"TopicArn": "...", "Message.$": "$.pedido.pedido_id"}, "End": true } } },
{ "StartAt": "PublicarAnalitica", "States": { "PublicarAnalitica": {
"Type": "Task", "Resource": "arn:aws:states:::events:putEvents",
"Parameters": {"Entries": [{"Source": "mercadofresco.tienda",
"DetailType": "PedidoProcesado", "EventBusName": "bus-mercadofresco",
"Detail.$": "$.pedido"}]}, "End": true } } }
],
"ResultPath": "$.notificaciones", "Next": "Exito"
}Compte: si una branca falla, tot el Parallel falla i la resta es cancel·len. Si vols tolerància,
posa Catch dins de cada branca.
Map — executa la mateixa sublògica per a cada element d'un array. És la diferència amb
Parallel: mateixes branques diferents vs. mateixa branca, dades diferents.
"ReservarCadaLinea": {
"Type": "Map",
"ItemsPath": "$.pedido.lineas",
"MaxConcurrency": 5,
"ItemSelector": { "sku.$": "$$.Map.Item.Value.sku", "unidades.$": "$$.Map.Item.Value.unidades" },
"ItemProcessor": {
"ProcessorConfig": { "Mode": "INLINE" },
"StartAt": "ReservarLinea",
"States": {
"ReservarLinea": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-reservar-stock", "Payload.$": "$" },
"End": true
}
}
},
"ResultPath": "$.reservas",
"Next": "PrepararEnAlmacen"
}$$ és l'objecte de context, que dona accés a metadades de l'execució: $$.Map.Item.Value és
l'element actual, $$.Execution.Name el nom de l'execució, $$.Task.Token el testimoni de retorn.
Wait — espera. Per segons fixos (Seconds), fins a una marca de temps (Timestamp), o amb el
valor pres de l'entrada (SecondsPath, TimestampPath).
"EsperarVentanaDeReparto": {
"Type": "Wait",
"TimestampPath": "$.reparto.inicio_franja",
"Next": "NotificarRepartidor"
}Un Wait de tres dies no consumeix res: Step Functions no manté cap procés esperant i no es cobren
transicions mentre espera. És una de les diferències més pràctiques respecte a implementar-ho a
mà.
Pass — no fa feina; transforma o injecta dades. Insubstituïble per depurar i per normalitzar
formes del JSON entre passos.
"NormalizarEntrada": {
"Type": "Pass",
"Parameters": { "pedido.$": "$.detail", "origen": "eventbridge" },
"Next": "CobrarPago"
}Succeed i Fail — acaben l'execució. Fail accepta Error i Cause, que són el que
apareixerà a l'historial i a la mètrica ExecutionsFailed; posa-hi textos útils.
"PedidoCompensado": {
"Type": "Fail",
"Error": "PedidoNoPreparable",
"Cause": "El almacen rechazo la preparacion; cobro devuelto y stock liberado."
}Flux de dades entre estats
Aquesta és la part que més costa, i mereix traçar-se pas a pas. Cada estat Task aplica cinc filtres
en aquest ordre exacte:
| Ordre | Camp | Què fa |
|---|---|---|
| 1 | InputPath |
Selecciona quina part de l'entrada es mira. Per defecte $ (tot) |
| 2 | Parameters |
Construeix la càrrega que s'envia al servei |
| 3 | (el servei respon) | — |
| 4 | ResultSelector |
Retalla i reordena la resposta crua del servei |
| 5 | ResultPath |
Diu on s'insereix el resultat dins de l'entrada original |
| 6 | OutputPath |
Selecciona què es passa a l'estat següent |
Els camps acabats en .$ prenen el seu valor d'una ruta JSONPath en lloc d'un literal. És la regla que
cal aprendre: "FunctionName": "mercadofresco-cobrar-pago" és un literal;
"Payload.$": "$.pedido" és una referència.
Exemple traçat. L'entrada de l'estat CobrarPago és:
{
"pedido": { "pedido_id": "PED-2026-084417", "importe_eur": 48.20, "cliente_id": "CLI-30912" },
"origen": "eventbridge"
}Amb l'estat definit més amunt:
InputPathno hi és, així que es pren tot ($).Parametersconstrueix{"FunctionName": "mercadofresco-cobrar-pago", "Payload": {"pedido_id": "PED-2026-084417", "importe_eur": 48.20, "cliente_id": "CLI-30912"}}. Només això arriba a Lambda.- Lambda respon, i la integració
lambda:invokeembolcalla la resposta:{"ExecutedVersion": "$LATEST", "Payload": {"referencia": "PAY-77321", "estado": "capturado"}, "StatusCode": 200}. ResultSelector{"referencia_cobro.$": "$.Payload.referencia"}retalla a{"referencia_cobro": "PAY-77321"}, llençant el soroll de la integració.ResultPath"$.cobro"insereix aquest objecte a l'entrada original sota la claucobro.OutputPathno hi és, així que surt tot.
Sortida final, que és l'entrada de ReservarStock:
{
"pedido": { "pedido_id": "PED-2026-084417", "importe_eur": 48.20, "cliente_id": "CLI-30912" },
"origen": "eventbridge",
"cobro": { "referencia_cobro": "PAY-77321" }
}Tres valors especials de ResultPath que cal conèixer:
| Valor | Efecte |
|---|---|
"$.alguna_cosa" |
Insereix el resultat sota aquesta clau i conserva l'entrada |
"$" (per defecte) |
El resultat substitueix tota l'entrada. Causa número u de dades perdudes |
null |
Descarta el resultat i passa l'entrada intacta. Ideal per a tasques amb resultat irrellevant |
L'error clàssic és deixar ResultPath per defecte en un Task intermedi: la resposta de Lambda esclafa
la comanda sencera i l'estat següent no troba $.pedido. Regla de MercadoFresco: en tot Task
intermedi, ResultPath explícit.
Integracions i patrons de servei
Step Functions crida altres serveis de dues maneres. Les integracions optimitzades tenen ARN propi
(arn:aws:states:::lambda:invoke, :::dynamodb:putItem, :::sns:publish, :::sqs:sendMessage,
:::ecs:runTask) i afegeixen comoditats. Les integracions de l'SDK cobreixen més de 200 serveis i
9.000 accions amb la forma arn:aws:states:::aws-sdk:<servei>:<accio>, per exemple
arn:aws:states:::aws-sdk:rds:describeDBClusters. Si alguna cosa es pot fer amb l'SDK, es pot fer sense
escriure una Lambda.
I hi ha tres patrons d'integració que canvien completament el comportament del Task:
| Patró | Sufix de l'ARN | Què fa | Exemple |
|---|---|---|---|
| Resposta | cap | Crida i continua tan bon punt respon l'API | sns:publish |
| Executar i esperar | .sync |
Espera que la feina acabi de debò | ecs:runTask.sync, states:startExecution.sync |
| Testimoni de retorn | .waitForTaskToken |
S'atura fins que algú retorna un testimoni | Pas humà, sistema extern |
La diferència entre resposta i .sync és enorme i sovint es malinterpreta. ecs:runTask retorna tan
bon punt ECS accepta la petició —la tasca pot trigar deu minuts més—; ecs:runTask.sync no continua
fins que la tasca acaba, i falla si la tasca falla. Sense .sync hauries de muntar un bucle de sondeig
amb Wait i Choice, que és exactament el que el patró t'estalvia.
El testimoni de retorn per al pas humà del magatzem
La preparació al magatzem no és una crida a una API: és una persona que agafa una caixa, l'omple i passa un lector de codis. Pot trigar dos minuts o una hora, i de vegades respon «no puc».
.waitForTaskToken està fet per a això. Step Functions genera un testimoni, el fica a la càrrega que
envia a la destinació i atura l'estat indefinidament (fins a HeartbeatSeconds o TimeoutSeconds).
L'execució no consumeix res mentre espera. Quan el sistema del magatzem acaba, crida SendTaskSuccess o
SendTaskFailure amb aquest testimoni, i la màquina continua.
"PrepararEnAlmacen": {
"Type": "Task",
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": {
"QueueUrl": "https://sqs.eu-west-1.amazonaws.com/111122223333/cola-mercadofresco-almacen",
"MessageBody": {
"pedido_id.$": "$.pedido.pedido_id",
"lineas.$": "$.pedido.lineas",
"franja.$": "$.pedido.franja_entrega",
"token_tarea.$": "$$.Task.Token"
}
},
"TimeoutSeconds": 3600,
"HeartbeatSeconds": 600,
"ResultPath": "$.preparacion",
"Retry": [
{ "ErrorEquals": ["States.Timeout"], "MaxAttempts": 1, "IntervalSeconds": 60 }
],
"Catch": [
{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "CompensarPedido" }
],
"Next": "AsignarReparto"
}El costat del magatzem, que és una aplicació normal:
sfn = boto3.client("stepfunctions", region_name="eu-west-1")
def confirmar_preparacio(token, comanda_id, operari):
"""El crida el terminal del magatzem quan la caixa esta llesta."""
sfn.send_task_success(taskToken=token, output=json.dumps(
{"preparado": True, "operario": operari, "pedido_id": comanda_id}))
def rebutjar_preparacio(token, motiu):
"""Producte en mal estat, trencament d'estoc real, incidencia de fred."""
sfn.send_task_failure(taskToken=token,
error="AlmacenNoPuedePreparar", # es compara a ErrorEquals
cause=motiu)
def continuo_treballant(token):
"""Batec: es crida cada pocs minuts mentre es prepara la caixa."""
sfn.send_task_heartbeat(taskToken=token)HeartbeatSeconds: 600 és la protecció contra l'operari que se'n va a dinar amb la caixa a mig fer: si
no arriba cap batec en 10 minuts, l'estat falla amb States.Heartbeat i el procés pot reaccionar molt
abans del TimeoutSeconds d'una hora. I error="AlmacenNoPuedePreparar" no és decoratiu: és el valor
que es compara a ErrorEquals per triar el camí de compensació.
Dos advertiments. El testimoni caduca amb l'execució: si la màquina s'atura, SendTaskSuccess
retornarà TaskTimedOut. I cal desar el testimoni en algun lloc persistent (al mateix missatge de la
cua, com aquí, o a DynamoDB), perquè és l'única cosa que permet reprendre.
Gestió d'errors: Retry i Catch
Retry reintenta el mateix estat. Catch abandona l'estat i salta a un altre. S'avaluen en aquest
ordre: primer s'esgoten els reintents, i només aleshores actua el Catch.
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException",
"States.TaskFailed", "PasarelaTemporalmenteNoDisponible"],
"IntervalSeconds": 2,
"MaxAttempts": 4,
"BackoffRate": 2.0,
"MaxDelaySeconds": 30,
"JitterStrategy": "FULL"
},
{
"ErrorEquals": ["TarjetaRechazada"],
"MaxAttempts": 0
}
]| Paràmetre | Què fa |
|---|---|
ErrorEquals |
Llista de noms d'error. States.ALL els captura tots |
IntervalSeconds |
Espera abans del primer reintent |
MaxAttempts |
Reintents (no intents). 0 desactiva el reintent per a aquest error |
BackoffRate |
Multiplicador entre reintents: 2 → 2 s, 4 s, 8 s, 16 s |
MaxDelaySeconds |
Sostre de l'interval, perquè el retrocés no es dispari |
JitterStrategy |
FULL aleatoritza l'espera i evita que mil execucions reintentin alhora |
JitterStrategy: "FULL" hauria de ser el teu valor per defecte. Sense ell, si la passarel·la de
pagament cau 30 segons, les 450 execucions en curs reintenten exactament en el mateix instant i la tomben
altre cop just quan s'estava recuperant. És la tempesta de reintents, que veurem a fons a 07-05.
L'ordre dels blocs importa. El primer que encaixi guanya, així que els errors específics van abans
que els genèrics. A l'exemple, TarjetaRechazada amb MaxAttempts: 0 desactiva explícitament el
reintent per a un error que no s'arregla reintentant: la targeta continuarà rebutjada. Distingir errors
transitoris de permanents és la mateixa disciplina de 07-01, ara declarativa.
Catch funciona igual, però en lloc de reintentar salta:
"Catch": [
{ "ErrorEquals": ["TarjetaRechazada"], "ResultPath": "$.error", "Next": "PedidoRechazado" },
{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "CompensarPedido" }
]ResultPath al Catch és crític. Amb "$.error", l'estat de destinació rep l'entrada original
més l'objecte {"Error": "...", "Cause": "..."} sota error. Sense ell (valor per defecte $),
l'error substitueix tota l'entrada i l'estat de compensació es queda sense saber quina comanda compensar
ni quina referència de cobrament tornar. És la fallada més frustrant de depurar a Step Functions.
Errors predefinits que convé conèixer: States.Timeout, States.TaskFailed, States.Permissions,
States.ResultPathMatchFailed, States.NoChoiceMatched, States.Heartbeat, States.DataLimitExceeded
(la càrrega entre estats ha superat els 256 KB).
El patró saga: compensar el que ja s'ha fet
No hi ha transaccions distribuïdes entre una passarel·la de pagament, Aurora, DynamoDB i l'ERP d'un soci. L'alternativa realista és la saga: una seqüència de passos on cadascun té una compensació que desfà el seu efecte, i si alguna cosa falla s'executen les compensacions dels passos ja completats, en ordre invers.
| Pas | Compensació |
|---|---|
| Cobrar la targeta | Tornar el cobrament (mercadofresco-devolver-cobro) |
| Reservar estoc | Alliberar estoc (mercadofresco-liberar-stock) |
| Preparar al magatzem | Cancel·lar l'ordre de preparació |
| Assignar repartiment | Alliberar la franja del repartidor |
La compensació no és un rollback: és una acció de negoci nova i visible. Una devolució apareix a l'extracte del client; els diners van estar retinguts. Per això les sagues es dissenyen per minimitzar el temps entre el pas arriscat i la seva possible compensació, i per això convé ordenar els passos de més a menys reversible quan es pugui, deixant els irreversibles per al final.
stateDiagram-v2
[*] --> CobrarPago
CobrarPago --> ReservarStock: cobrament OK
CobrarPago --> PedidoRechazado: TarjetaRechazada
ReservarStock --> PrepararEnAlmacen: estoc reservat
ReservarStock --> DevolverCobro: SinStock
PrepararEnAlmacen --> AsignarReparto: token success
PrepararEnAlmacen --> LiberarStock: AlmacenNoPuedePreparar / Timeout / Heartbeat
AsignarReparto --> NotificarCliente: repartiment assignat
AsignarReparto --> LiberarStock: SinRepartidor
NotificarCliente --> PedidoCompletado
LiberarStock --> DevolverCobro: estoc alliberat
DevolverCobro --> PedidoCompensado: cobrament tornat
DevolverCobro --> RevisionManual: fallada en tornar
PedidoCompletado --> [*]
PedidoCompensado --> [*]
PedidoRechazado --> [*]
RevisionManual --> [*]
Fixa't en RevisionManual. Les compensacions també fallen, i quan falla una devolució hi ha diners
d'un client retinguts sense comanda: això no pot acabar en un Fail silenciós. Aquest estat publica a
alertas-mercadofresco i escriu en una cua que la Marta revisa. Tota saga necessita el seu camí de
«això ja és per a un humà».
mercadofresco-procesar-pedido completa
{
"Comment": "Proceso de pedido de MercadoFresco con saga de compensacion",
"StartAt": "NormalizarEntrada",
"TimeoutSeconds": 5400,
"States": {
"NormalizarEntrada": {
"Type": "Pass",
"Parameters": { "pedido.$": "$.detail", "iniciado_en.$": "$$.Execution.StartTime" },
"Next": "CobrarPago"
},
"CobrarPago": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-cobrar-pago", "Payload.$": "$.pedido" },
"ResultSelector": { "referencia.$": "$.Payload.referencia" },
"ResultPath": "$.cobro", "TimeoutSeconds": 30,
"Retry": [
{ "ErrorEquals": ["TarjetaRechazada"], "MaxAttempts": 0 },
{ "ErrorEquals": ["States.ALL"], "IntervalSeconds": 2, "MaxAttempts": 4,
"BackoffRate": 2.0, "MaxDelaySeconds": 30, "JitterStrategy": "FULL" }
],
"Catch": [{ "ErrorEquals": ["TarjetaRechazada"], "ResultPath": "$.error",
"Next": "PedidoRechazado" }],
"Next": "ReservarStock"
},
"ReservarStock": {
"Type": "Map", "ItemsPath": "$.pedido.lineas", "MaxConcurrency": 5,
"ItemSelector": { "sku.$": "$$.Map.Item.Value.sku",
"unidades.$": "$$.Map.Item.Value.unidades",
"pedido_id.$": "$.pedido.pedido_id" },
"ItemProcessor": {
"ProcessorConfig": { "Mode": "INLINE" }, "StartAt": "ReservarLinea",
"States": { "ReservarLinea": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-reservar-stock", "Payload.$": "$" },
"ResultSelector": { "reservado.$": "$.Payload.reservado" }, "End": true } }
},
"ResultPath": "$.reservas",
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error",
"Next": "DevolverCobro" }],
"Next": "PrepararEnAlmacen"
},
"PrepararEnAlmacen": {
"Type": "Task", "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": {
"QueueUrl": "https://sqs.eu-west-1.amazonaws.com/111122223333/cola-mercadofresco-almacen",
"MessageBody": { "pedido_id.$": "$.pedido.pedido_id", "lineas.$": "$.pedido.lineas",
"franja.$": "$.pedido.franja_entrega", "token_tarea.$": "$$.Task.Token" }
},
"TimeoutSeconds": 3600, "HeartbeatSeconds": 600, "ResultPath": "$.preparacion",
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error",
"Next": "LiberarStock" }],
"Next": "AsignarReparto"
},
"AsignarReparto": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-asignar-reparto",
"Payload": { "pedido_id.$": "$.pedido.pedido_id",
"franja.$": "$.pedido.franja_entrega",
"provincia.$": "$.pedido.provincia" } },
"ResultSelector": { "repartidor_id.$": "$.Payload.repartidor_id", "eta.$": "$.Payload.eta" },
"ResultPath": "$.reparto",
"Retry": [{ "ErrorEquals": ["States.ALL"], "IntervalSeconds": 5, "MaxAttempts": 3,
"BackoffRate": 2.0, "JitterStrategy": "FULL" }],
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error",
"Next": "LiberarStock" }],
"Next": "NotificarCliente"
},
"NotificarCliente": {
"Type": "Task", "Resource": "arn:aws:states:::events:putEvents",
"Parameters": { "Entries": [{
"EventBusName": "bus-mercadofresco", "Source": "mercadofresco.tienda",
"DetailType": "PedidoProcesado",
"Detail": { "pedido_id.$": "$.pedido.pedido_id",
"repartidor_id.$": "$.reparto.repartidor_id", "eta.$": "$.reparto.eta" } }] },
"ResultPath": null, "Next": "PedidoCompletado"
},
"PedidoCompletado": { "Type": "Succeed" },
"LiberarStock": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-liberar-stock",
"Payload": { "pedido_id.$": "$.pedido.pedido_id",
"lineas.$": "$.pedido.lineas" } },
"ResultPath": null,
"Retry": [{ "ErrorEquals": ["States.ALL"], "IntervalSeconds": 5, "MaxAttempts": 5,
"BackoffRate": 2.0, "JitterStrategy": "FULL" }],
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error_compensacion",
"Next": "RevisionManual" }],
"Next": "DevolverCobro"
},
"DevolverCobro": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-devolver-cobro",
"Payload": { "referencia.$": "$.cobro.referencia",
"pedido_id.$": "$.pedido.pedido_id" } },
"ResultPath": null,
"Retry": [{ "ErrorEquals": ["States.ALL"], "IntervalSeconds": 10, "MaxAttempts": 6,
"BackoffRate": 2.0, "MaxDelaySeconds": 300, "JitterStrategy": "FULL" }],
"Catch": [{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error_compensacion",
"Next": "RevisionManual" }],
"Next": "PedidoCompensado"
},
"RevisionManual": {
"Type": "Task", "Resource": "arn:aws:states:::sns:publish",
"Parameters": { "TopicArn": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
"Subject": "Compensacion fallida: revision manual",
"Message.$": "States.JsonToString($)" },
"Next": "PedidoIncoherente"
},
"PedidoIncoherente": { "Type": "Fail", "Error": "CompensacionFallida",
"Cause": "No se pudo devolver el cobro o liberar el stock. Requiere intervencion humana." },
"PedidoCompensado": { "Type": "Fail", "Error": "PedidoNoPreparable",
"Cause": "El pedido se compenso correctamente: cobro devuelto y stock liberado." },
"PedidoRechazado": { "Type": "Fail", "Error": "TarjetaRechazada",
"Cause": "La pasarela rechazo el pago. No hay nada que compensar." }
}
}Detalls que no són casuals. ResultPath: null a les compensacions i a la notificació: el seu
resultat no aporta res i així no s'embruta l'estat. Més reintents a DevolverCobro (6) que a la
resta: fallar una devolució és molt més car que fallar qualsevol altra cosa. PedidoRechazado no
passa per compensació, perquè si la targeta es va rebutjar no es va cobrar res. I
States.JsonToString($) és una de les funcions intrínseques del llenguatge; n'hi ha més
(States.Format, States.Array, States.ArrayPartition, States.MathRandom, States.UUID) que
eviten haver d'escriure una Lambda només per transformar dades.
Map distribuït per al volum del divendres
El Map en mode INLINE té dos límits: 40 iteracions concurrents i totes les dades a l'estat,
subjectes al màxim de 256 KB. Per conciliar les 4.300 comandes d'un divendres contra els apunts de la
passarel·la, o per processar un fitxer de 200.000 línies a S3, cal el mode
DISTRIBUTED.
"ConciliarPedidosDelDia": {
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:getObject",
"ReaderConfig": { "InputType": "CSV", "CSVHeaderLocation": "FIRST_ROW" },
"Parameters": { "Bucket": "mercadofresco-informes-analitica",
"Key.$": "$.fichero_conciliacion" }
},
"ItemProcessor": {
"ProcessorConfig": { "Mode": "DISTRIBUTED", "ExecutionType": "EXPRESS" },
"StartAt": "ConciliarPedido",
"States": {
"ConciliarPedido": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "mercadofresco-conciliar-pedido", "Payload.$": "$" },
"End": true
}
}
},
"MaxConcurrency": 200,
"ToleratedFailurePercentage": 2,
"ItemBatcher": { "MaxItemsPerBatch": 25 },
"ResultWriter": {
"Resource": "arn:aws:states:::s3:putObject",
"Parameters": { "Bucket": "mercadofresco-informes-analitica",
"Prefix": "conciliacion/resultados/" }
},
"Next": "PublicarInformeConciliacion"
}Les diferències amb el mode INLINE són substancials:
INLINE |
DISTRIBUTED |
|
|---|---|---|
| Concurrència màxima | 40 | 10.000 |
| Origen dels elements | Un array a l'estat | Array, o S3: objectes, CSV, JSON, manifest |
| Execució de cada iteració | Dins de l'execució pare | Execució filla independent |
| Historial | Compta al de la pare | Propi, sense inflar el de la pare |
| Tolerància a fallades | Tot o res | ToleratedFailurePercentage |
| Volum pràctic | Centenars | Milions |
ToleratedFailurePercentage: 2 permet que fins a un 2 % dels apunts falli sense avortar la conciliació
sencera —el típic quan el fitxer del banc porta línies rares— i ItemBatcher agrupa 25 elements per
invocació de Lambda, dividint per 25 el nombre d'invocacions i el seu cost. Com que cada iteració és una
execució filla, l'historial de la pare no s'omple amb 4.300 entrades, que és el que feia inservible la
consola amb el mode INLINE.
Desplegament i arrencada des d'EventBridge
export PERFIL="--profile mercadofresco-dev --region eu-west-1"
aws stepfunctions create-state-machine \
--name mercadofresco-procesar-pedido \
--definition file://mercadofresco-procesar-pedido.json \
--role-arn arn:aws:iam::111122223333:role/rol-mf-step-functions \
--type STANDARD \
--logging-configuration '{"level":"ERROR","includeExecutionData":true,
"destinations":[{"cloudWatchLogsLogGroup":{"logGroupArn":
"arn:aws:logs:eu-west-1:111122223333:log-group:/aws/vendedlogs/states/mercadofresco:*"}}]}' \
--tracing-configuration '{"enabled":true}' \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=integracion Key=Propietario,Value=marta \
Key=CentroCoste,Value=tecnologia $PERFILEl rol rol-mf-step-functions necessita permís per a cada acció que la màquina executa:
lambda:InvokeFunction sobre les cinc funcions, sqs:SendMessage sobre la cua del magatzem,
events:PutEvents sobre el bus, sns:Publish sobre el tema d'alertes, més
xray:PutTraceSegments i els permisos d'escriptura al grup de registres. Un States.Permissions
enmig d'una compensació és de les fallades més desagradables que hi ha: la comanda queda a mitges
perquè l'orquestrador no podia cridar qui tocava.
L'arrencada la fa una regla de bus-mercadofresco:
aws events put-rule --name regla-mf-arrancar-proceso-pedido \
--event-bus-name bus-mercadofresco --state ENABLED \
--event-pattern '{"source":["mercadofresco.tienda"],"detail-type":["PedidoConfirmado"]}' $PERFIL
aws events put-targets --rule regla-mf-arrancar-proceso-pedido \
--event-bus-name bus-mercadofresco \
--targets '[{
"Id": "maquina-procesar-pedido",
"Arn": "arn:aws:states:eu-west-1:111122223333:stateMachine:mercadofresco-procesar-pedido",
"RoleArn": "arn:aws:iam::111122223333:role/rol-mf-eventbridge-invoca-sfn",
"DeadLetterConfig": {"Arn":"arn:aws:sqs:eu-west-1:111122223333:mercadofresco-eventbridge-fallidas"},
"RetryPolicy": {"MaximumRetryAttempts": 5, "MaximumEventAgeInSeconds": 3600}
}]' $PERFILUn truc molt recomanable: passar com a nom d'execució un valor derivat del pedido_id. Els noms
d'execució han de ser únics en 90 dies, així que si el mateix esdeveniment arriba dues vegades, la
segona execució falla amb ExecutionAlreadyExists en lloc de cobrar la targeta un altre cop. És
idempotència de franc, i enllaça directament amb 07-05.
Observabilitat: depurar una execució fallida
Step Functions desa l'historial complet de cada execució estàndard durant 90 dies: cada entrada d'estat, cada sortida, cada reintent, cada error. És la seva millor característica i no té equivalent en una orquestració escrita a mà.
# Execucions fallides del dia
aws stepfunctions list-executions \
--state-machine-arn arn:aws:states:eu-west-1:111122223333:stateMachine:mercadofresco-procesar-pedido \
--status-filter FAILED --max-items 20 $PERFIL
# L'historial complet d'una d'elles
aws stepfunctions get-execution-history \
--execution-arn arn:aws:states:eu-west-1:111122223333:execution:mercadofresco-procesar-pedido:PED-2026-084417 \
--reverse-order --max-items 40 $PERFILUna execució fallida real. La Marta veu una execució en FAILED i recorre l'historial cap enrere:
ExecutionFailedambError: "PedidoNoPreparable"→ va acabar aPedidoCompensado, així que la compensació va funcionar. Bon senyal: el client té els seus diners.TaskStateExiteddeDevolverCobroi deLiberarStock→ les dues compensacions es van executar.TaskFailedaPrepararEnAlmacenambError: "States.Heartbeat"→ no va ser un rebuig del magatzem, va ser falta de batec. El terminal va deixar d'enviarSendTaskHeartbeat.TaskScheduleddePrepararEnAlmacena les 18:47,TaskFaileda les 18:57 → exactament els 600 segons deHeartbeatSeconds.
Diagnòstic: no és un problema de negoci, és que el terminal del magatzem perd la connexió wifi a la
cambra de fred i deixa d'enviar batecs. La correcció no és a la màquina d'estats —que es va comportar
correctament— sinó al terminal, que ha de reintentar el batec, i a apujar HeartbeatSeconds a 900 per
tolerar els talls coneguts. Sense l'historial, aquest diagnòstic hauria trigat dies.
Amb --tracing-configuration '{"enabled":true}', X-Ray (05-02) mostra el mapa de serveis de l'execució
i on se'n va anar el temps: quant a la passarel·la, quant esperant el magatzem, quant a
Aurora. I les mètriques de CloudWatch a vigilar són ExecutionsFailed, ExecutionsTimedOut,
ExecutionsAborted i ExecutionTime, totes amb alarma cap a alertas-mercadofresco.
Un detall de configuració: "level": "ERROR" registra només els estats fallits. "ALL" registra tot i
és utilíssim mentre desenvolupes, però en producció, amb 240.000 execucions al mes i
includeExecutionData: true, el volum de CloudWatch Logs pot costar més que la mateixa màquina.
Workflow Studio i cost
Workflow Studio és l'editor visual de la consola: arrossegues estats, configures integracions des de formularis i veus el JSON generar-se en temps real, amb validació al vol. És la millor manera d'aprendre el llenguatge i d'explorar les 9.000 accions de l'SDK, i també de dibuixar el primer esborrany d'un flux amb la Marta al davant. Per a producció, la definició viu al repositori i es desplega amb CloudFormation o CDK (mòdul 9): l'editor visual serveix per dissenyar i per llegir, no per ser la font de la veritat.
Cost. Els fluxos estàndard es cobren per transició d'estat: 4.000 de franc al mes i després
0,025 USD per 1.000. mercadofresco-procesar-pedido recorre uns 9 estats per comanda en el camí feliç.
| Escenari | Transicions/mes | Cost |
|---|---|---|
| 240.000 comandes × 9 estats (estàndard) | 2.160.000 | 54,00 USD |
| Compensacions (1,2 % × 4 estats extra) | 11.520 | 0,29 USD |
mercadofresco-validar-carrito, exprés, 1,4 M execucions de 200 ms i 64 MB |
— | ~2,20 USD |
Conciliació diària (DISTRIBUTED, 30 × 4.300 filles exprés) |
— | ~1,10 USD |
La comparació estàndard/exprés és reveladora: el mateix procés en exprés costaria uns 3 USD en lloc de
54. Per què no fer servir exprés per a tot, doncs? Perquè el procés de comanda dura fins a una hora
(el màxim d'exprés són 5 minuts), necessita waitForTaskToken asíncron, necessita semàntica d'exactament
una vegada per no cobrar dues vegades, i necessita l'historial de 90 dies per a les
reclamacions. Els 51 USD de diferència compren precisament això, i per a 240.000 comandes amb un
import mitjà de 48 € és una fracció menyspreable de la facturació.
La regla pràctica: exprés per al que és curt, idempotent i d'alt volum; estàndard per al que és llarg, crític i s'ha de poder auditar.
aws stepfunctions delete-state-machine \
--state-machine-arn arn:aws:states:eu-west-1:111122223333:stateMachine:mercadofresco-procesar-pedido $PERFIL
aws events remove-targets --rule regla-mf-arrancar-proceso-pedido \
--event-bus-name bus-mercadofresco --ids maquina-procesar-pedido $PERFIL
aws events delete-rule --name regla-mf-arrancar-proceso-pedido \
--event-bus-name bus-mercadofresco $PERFILEsborrar una màquina d'estats no cancel·la les execucions en curs: passen a ABORTED quan acabin els
passos actuals, i les que esperen un testimoni queden òrfenes.
Errors Habituals i Consells
Deixar ResultPath per defecte en un Task intermedi. El resultat substitueix tota l'entrada i
l'estat següent no troba les seves dades. Símptoma: States.Runtime amb «no s'ha pogut resoldre la
ruta». Posa ResultPath explícit sempre, o null si el resultat no importa.
Oblidar ResultPath al Catch. L'estat de compensació rep només l'error i no sap quina comanda
compensar ni quin cobrament tornar. És la fallada més frustrant d'aquesta lliçó.
Choice sense Default. Un cas no previst acaba en States.NoChoiceMatched i l'execució mor sense
compensar res.
No distingir errors transitoris de permanents al Retry. Reintentar quatre vegades una
TarjetaRechazada endarrereix la resposta al client i no arregla res. Declara MaxAttempts: 0 per als
errors permanents, i posa'ls abans del bloc genèric.
Retry sense JitterStrategy: "FULL". Quan la dependència es recupera, totes les execucions
reintenten alhora i la tornen a tombar.
Compensacions sense el seu propi Catch. Si la devolució falla i no hi ha camí a revisió manual, el
client es queda sense comanda i sense diners, i ningú no se n'assabenta.
Passar càrregues grans entre estats. El límit és 256 KB. Un Map amb 5.000 elements peta amb
States.DataLimitExceeded. Passa referències a S3, no continguts.
waitForTaskToken sense HeartbeatSeconds ni TimeoutSeconds. L'execució es queda esperant fins a
l'any, ocupant una ranura i sense que ningú se n'adoni.
Fer servir exprés per a processos amb efectes no idempotents. La semàntica d'almenys una vegada significa que un cobrament es pot executar dues vegades.
Parallel sense Catch per branca. La fallada d'una branca cancel·la les altres, incloses les que
ja anaven per la meitat.
Consell: anomena l'execució amb l'identificador de negoci. PED-2026-084417 com a nom d'execució
dona idempotència de franc, i cercar a la consola passa d'impossible a immediat.
Consell: valida el flux de dades amb Pass abans d'escriure la lògica. Munta la màquina sencera amb
estats Pass que retornen dades fictícies, comprova que el JSON arriba bé d'un extrem a l'altre, i
només aleshores substitueix cada Pass pel seu Task.
Consell: "level": "ALL" en desenvolupament, "ERROR" en producció. El registre complet amb dades
d'execució és caríssim a 240.000 execucions al mes.
Exercicis
Exercici 1: la devolució d'una comanda lliurada
Dissenya mercadofresco-procesar-devolucion per quan un client torna una comanda ja lliurada. Passos:
(1) validar que la devolució és dins de termini (48 h); (2) generar l'etiqueta de recollida cridant
l'API del transportista; (3) esperar que el transportista confirmi la recollida, que pot trigar fins a 3
dies; (4) quan arribi al magatzem, un operari inspecciona l'estat del producte i decideix acceptar,
acceptar parcialment o rebutjar; (5) segons la decisió, tornar l'import total, parcial o cap; (6)
reposar l'estoc només si es va acceptar.
Indica: (a) estàndard o exprés i per què; (b) quin tipus d'estat fas servir a cada pas; (c) com modeles
els passos 3 i 4; (d) quin Retry i quin Catch poses al pas 5; (e) quines compensacions necessites i
quines no.
Exercici 2: traçar el flux de dades
Donat aquest estat i aquesta entrada, escriu la sortida exacta que rep l'estat següent.
"ComprobarCliente": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"InputPath": "$.pedido",
"Parameters": { "FunctionName": "mercadofresco-datos-cliente", "Payload": { "id.$": "$.cliente_id" } },
"ResultSelector": { "nivel.$": "$.Payload.nivel_fidelidad", "email.$": "$.Payload.email" },
"ResultPath": "$.cliente",
"OutputPath": "$",
"Next": "AplicarDescuento"
}Entrada: {"pedido": {"pedido_id": "PED-1", "cliente_id": "CLI-9", "importe_eur": 30.0}, "origen": "web"}. La Lambda retorna {"nivel_fidelidad": "oro", "email": "[email protected]", "telefono": "+34600000000"}.
Respon: (a) què rep exactament la Lambda; (b) la sortida completa de l'estat; (c) què passaria si es
tragués ResultPath; (d) què passaria si OutputPath fos "$.cliente"; (e) per què InputPath no
afecta on insereix ResultPath.
Exercici 3: coreografia o orquestració
Per a cada cas, decideix i justifica en dues frases: (a) en confirmar una comanda cal avisar quatre
equips independents; (b) l'alta d'un proveïdor requereix validar documents, aprovació de dues persones i
alta a Aurora, amb termini de 10 dies; (c) cal redimensionar 8.000 fotos del catàleg cada nit; (d) en
detectar StockBajo cal demanar al proveïdor, esperar confirmació i actualitzar la data prevista; (e)
cal registrar cada canvi de preu a Redshift per a auditoria.
Solucions
Solució 1
(a) Estàndard, sens dubte. El pas 3 pot durar 3 dies, molt per damunt dels 5 minuts d'exprés. A més hi ha diners pel mig i cal la semàntica d'exactament una vegada i l'historial de 90 dies per a les reclamacions.
(b) i (c) Els estats. (1) Choice comparant $$.Execution.StartTime amb la data de lliurament;
si és fora de termini, Fail amb Error: "FueraDePlazoDevolucion" sense compensar res, perquè encara no
s'ha fet res. (2) Task amb Retry i JitterStrategy: FULL cap a l'API del transportista.
(3) Task amb .waitForTaskToken i TimeoutSeconds: 259200 (3 dies): el webhook del transportista
crida SendTaskSuccess; un Wait no serviria perquè no sabem quan passarà, només el termini màxim,
i aquí no convé HeartbeatSeconds perquè el transportista no envia batecs. (4) Un altre Task
amb .waitForTaskToken cap a la cua del magatzem, amb TimeoutSeconds: 86400; la decisió de l'operari
arriba a l'output del SendTaskSuccess i un Choice posterior encamina segons
$.inspeccion.decision (aceptada/parcial/rechazada), amb Default a revisió manual.
(5) Task de devolució amb import calculat. (6) Map per línia per reposar estoc.
(d) Retry i Catch del pas 5. Retry generós —6 intents, IntervalSeconds: 10,
BackoffRate: 2, MaxDelaySeconds: 300, JitterStrategy: FULL— perquè una devolució fallida és el
pitjor que pot passar aquí. Catch sobre States.ALL amb ResultPath: "$.error" cap a un estat
RevisionManualDevolucion que publica a alertas-mercadofresco. Mai un Fail directe: deixaria el
client sense producte i sense diners.
(e) Compensacions. Aquí n'hi ha moltes menys que al procés de comanda, i el motiu és interessant: el flux va de menys a més compromès, i els passos irreversibles són al final. L'etiqueta de recollida sí que necessita compensació (cancel·lar-la) si la devolució s'avorta abans de la recollida. La inspecció no necessita compensació: és una lectura. La devolució de l'import no es compensa tornant a cobrar —això seria inacceptable—; si es detecta un error posterior s'obre una incidència manual. I la reposició d'estoc es compensa retirant-lo si després es descobreix que el producte no era apte. Lliçó general: ordenar els passos de més reversible a menys reversible redueix el nombre de compensacions necessàries.
Solució 2
(a) Què rep la Lambda. InputPath: "$.pedido" redueix l'entrada a
{"pedido_id": "PED-1", "cliente_id": "CLI-9", "importe_eur": 30.0}. Sobre això, Parameters construeix
la càrrega, i "id.$": "$.cliente_id" es resol contra el resultat d'InputPath, no contra l'entrada
original. La Lambda rep exactament:
(b) Sortida completa. La resposta de la integració és
{"ExecutedVersion": "...", "Payload": {"nivel_fidelidad": "oro", "email": "[email protected]", "telefono": "+34600000000"}, "StatusCode": 200}. ResultSelector la retalla a
{"nivel": "oro", "email": "[email protected]"} —el telèfon es descarta— i ResultPath: "$.cliente" la
insereix a l'entrada original completa, no a la retallada per InputPath. Amb OutputPath: "$"
surt tot:
{
"pedido": { "pedido_id": "PED-1", "cliente_id": "CLI-9", "importe_eur": 30.0 },
"origen": "web",
"cliente": { "nivel": "oro", "email": "[email protected]" }
}(c) Sense ResultPath. El valor per defecte és $, així que el resultat substitueix tota l'entrada.
La sortida seria {"nivel": "oro", "email": "[email protected]"} i AplicarDescuento no trobaria
$.pedido.importe_eur, i fallaria amb un error de resolució de ruta. És l'error més comú del llenguatge.
(d) Amb OutputPath: "$.cliente". La sortida seria només {"nivel": "oro", "email": "[email protected]"}. Es perd la comanda igualment, però per un motiu diferent: no és que el resultat
l'hagi substituïda, és que s'ha retallat al final. Il·lustra bé que ResultPath i OutputPath actuen en
moments diferents i poden espatllar el mateix per vies diferents.
(e) Per què InputPath no afecta ResultPath. InputPath només determina què es passa a
Parameters per construir la crida; l'estat conserva internament l'entrada original i és sobre
aquesta que ResultPath insereix. Aquesta separació és deliberada i molt útil: permet enviar al servei
una vista reduïda sense perdre el context acumulat del procés.
Solució 3
(a) Coreografia. Quatre interessats independents en el mateix fet, sense dependències entre ells i sense res a compensar: és fan-out pur. SNS o EventBridge, segons si cal encaminar per contingut.
(b) Orquestració. Procés llarg (10 dies), amb passos dependents, dues intervencions humanes
—waitForTaskToken per partida doble— i necessitat de saber en quin punt va cada alta. Amb esdeveniments
caldria inventar una taula d'estat i un vigilant, que és reimplementar Step Functions pitjor.
(c) Cap dels dos com a orquestració clàssica: un Map distribuït, o simplement S3 → SQS →
Lambda si no cal control del conjunt. Són 8.000 tasques independents i idempotents; el que es necessita
és paral·lelisme i tolerància a fallades parcials, no un flux amb estat. Si a més vols saber quan van
acabar totes i amb quina taxa d'error, el Map distribuït amb ToleratedFailurePercentage i
ResultWriter és l'opció correcta.
(d) Orquestració. Hi ha una espera de durada desconeguda (la confirmació del proveïdor) i passos
encadenats. Màquina estàndard arrencada per la regla de StockBajo, amb waitForTaskToken per a la
confirmació i Wait/Choice per reclamar si el proveïdor no contesta en 24 hores.
(e) Coreografia. Un fet solt que interessa a un consumidor, sense dependències ni compensació.
Esdeveniment a bus-mercadofresco → cua → càrrega a Redshift. Ficar-hi Step Functions només afegiria
cost per transició i una peça més per mantenir.
Conclusió
El procés de comanda de MercadoFresco té ara un amo explícit.
mercadofresco-procesar-pedido sap cobrar, reservar l'estoc línia a línia amb un Map, esperar fins a
una hora que una persona del magatzem confirmi la caixa mitjançant un testimoni de retorn amb batec,
assignar repartiment, publicar PedidoProcesado a bus-mercadofresco i —el que cap coreografia sabia
fer— desfer el que s'ha fet quan alguna cosa falla: alliberar l'estoc, tornar el cobrament i, si fins
i tot la compensació falla, avisar la Marta per alertas-mercadofresco en lloc de morir en silenci.
L'historial de 90 dies va convertir un misteri («per què s'ha compensat aquesta comanda?») en quatre
entrades llegides de baix a dalt: el terminal del magatzem perdia el wifi a la cambra de fred.
Les idees que cal endur-se: l'elecció entre coreografia i orquestració no és de gust, la dicten les
dependències entre passos i la necessitat de compensar; estàndard enfront d'exprés es decideix per
durada, semàntica d'execució i necessitat d'auditoria, i els 51 USD de diferència mensual compren
exactament això; el flux de dades —InputPath, Parameters, ResultSelector, ResultPath,
OutputPath— és on es perden més hores, i la regla d'or és ResultPath explícit en tot Task
intermedi; Retry amb fluctuació (jitter) i errors classificats evita que la recuperació d'una
dependència la torni a tombar; i tota saga necessita el seu camí a revisió manual, perquè les
compensacions també fallen.
Amb això, MercadoFresco té les quatre peces d'integració: cues per desacoblar, temes per repartir, un bus per encaminar per contingut i fluxos per orquestrar. Però queden preguntes que travessen les quatre i que fins ara hem anat ajornant. Què significa exactament «almenys una vegada» quan el que es duplica és un cobrament? Com s'escriu un consumidor que pugui rebre el mateix missatge tres vegades sense cobrar tres vegades? Quantes vegades cal reintentar, i quan cal deixar de fer-ho per no tombar qui s'està recuperant? Què es fa exactament amb els missatges d'una DLQ un dilluns al matí? I com es garanteix que una comanda escrita a Aurora es publica sempre, si no hi ha transacció que abasti la base de dades i el bus?
A 07-05, «Patrons d'integració», tanquem el mòdul amb les respostes: garanties de lliurament i idempotència amb taula de deduplicació a DynamoDB, retrocés exponencial amb fluctuació, interruptor de circuit per al proveïdor de pagaments, el runbook de la DLQ, ordre i agrupació, el patró outbox per a la doble escriptura, contrapressió, i una taula decisòria final que respon d'una vegada la pregunta «SQS, SNS, EventBridge, Step Functions o una crida síncrona?».
Curs d'AWS
Mòdul 1: Introducció a AWS
- Què és AWS?
- Configuració del teu compte d'AWS
- Infraestructura global d'AWS
- Consola d'administració d'AWS
- AWS CLI i SDK
Mòdul 2: Serveis principals d'AWS
Mòdul 3: Xarxes i lliurament de contingut
- Amazon VPC
- Grups de seguretat i llistes de control d'accés
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Mòdul 4: Seguretat i identitat
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager i Parameter Store
- AWS Shield
- AWS WAF
Mòdul 5: Monitoratge i gestió
- Amazon CloudWatch
- AWS X-Ray i traçabilitat distribuïda
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Mòdul 6: Bases de dades
- Com triar la base de dades adequada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Mòdul 7: Integració d'aplicacions
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrons d'integració: idempotència, reintents i cues de missatges fallits
