Orquestación Serverless: Step Functions vs Temporal
Coordina workflows serverless de varios pasos con Step Functions, Temporal o Durable Functions: estado, reintentos y errores gestionados.
Visión general
Una sola función AWS Lambda puede procesar una petición HTTP, redimensionar una imagen o validar un formulario. Pero los workflows del mundo real raramente son tan simples. Un pedido de e-commerce implica validar inventario, cobrar el pago, enviar la confirmación, actualizar analytics y programar el envío. Cada paso es una función; el workflow es la lógica de coordinación que decide qué llamar a continuación, qué hacer ante fallas y cómo reintentar errores transitorios. Esa capa de coordinación — una máquina de estados gestionada que invoca tus funciones en orden — es la orquestación serverless.
Escribir esta coordinación dentro de funciones Lambda crea código spaghetti: la función A llama a B, que llama a C, que llama a D. Si D falla, C debe manejar la compensación, que también puede fallar, requiriendo más manejo de errores. La lógica de negocio se entrelaza con networking, reintentos, timeouts y persistencia de estado. Las herramientas de orquestación de workflows — AWS Step Functions, Azure Durable Functions, Temporal — externalizan esta coordinación en máquinas de estados. Las funciones se convierten en lógica pura de negocio; el orquestador maneja el flujo de ejecución, estado, reintentos y recuperación. Los ejemplos de abajo cubren el diseño de máquinas de estados, los trade-offs de cada orquestador y un workflow de pedidos listo para producción en las tres plataformas.
Cuándo usarlo
Usa esta receta cuando:
- Un proceso serverless tiene más de dos pasos dependientes. Consulta Funciones Event-Driven para construir unidades de función individuales.
- Los pasos deben reintentarse con backoff ante fallas transitorias. Consulta Lógica de Reintentos para patrones de backoff exponencial.
- El estado del workflow debe sobrevivir a timeouts o crashes de funciones.
- Se requieren esperas de aprobación humana o eventos externos a mitad del workflow.
- Varias funciones deben ejecutarse en ramas paralelas con puntos de unión. Para un deep dive exclusivo de AWS con map states y procesamiento por lotes, consulta la receta de workflows con Step Functions.
Evita el orquestador cuando una sola función o una cadena síncrona simple resuelve el caso: una máquina de estados suma costo por transición y un sistema más que operar.
Solución
El workflow de procesamiento de pedidos de abajo es idéntico en las tres implementaciones: validar el pedido, comprobar inventario, cobrar el pago, fan-out a email y analytics, y programar el envío. Copias ejecutables de las tres están en el repositorio complementario.
AWS Step Functions (ASL, Amazon States Language)
{
"Comment": "Order Processing Workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:validateOrder",
"Next": "CheckInventory",
"Catch": [
{
"ErrorEquals": ["ValidationException"],
"Next": "NotifyCustomerInvalid"
}
]
},
"CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:checkInventory",
"Next": "ProcessPayment",
"Retry": [
{
"ErrorEquals": ["ServiceUnavailable"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2
}
]
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:processPayment",
"Next": "ParallelTasks",
"Catch": [
{
"ErrorEquals": ["PaymentFailed"],
"Next": "ReleaseInventory"
}
]
},
"ParallelTasks": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "SendEmail",
"States": {
"SendEmail": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:sendEmail",
"End": true
}
}
},
{
"StartAt": "UpdateAnalytics",
"States": {
"UpdateAnalytics": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:updateAnalytics",
"End": true
}
}
}
],
"Next": "ScheduleShipment"
},
"ScheduleShipment": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:scheduleShipment",
"End": true
},
"ReleaseInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:releaseInventory",
"Next": "NotifyCustomerFailed"
},
"NotifyCustomerInvalid": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:notifyCustomer",
"End": true
},
"NotifyCustomerFailed": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789:function:notifyCustomer",
"End": true
}
}
}
Temporal Workflow (TypeScript SDK)
import { workflow, activity } from '@temporalio/workflow';
const validateOrder = activity('validateOrder');
const checkInventory = activity('checkInventory');
const processPayment = activity('processPayment');
const sendEmail = activity('sendEmail');
const updateAnalytics = activity('updateAnalytics');
const scheduleShipment = activity('scheduleShipment');
const releaseInventory = activity('releaseInventory');
export async function orderWorkflow(order: OrderInput): Promise<OrderResult> {
try {
await validateOrder(order);
await checkInventory(order.items);
await processPayment({ orderId: order.id, amount: order.total });
await Promise.all([
sendEmail({ orderId: order.id, template: 'confirmation' }),
updateAnalytics({ event: 'order_placed', orderId: order.id }),
]);
await scheduleShipment({ orderId: order.id, address: order.address });
return { status: 'completed', orderId: order.id };
} catch (error) {
if (error.type === 'PaymentFailed') {
await releaseInventory({ orderId: order.id });
}
throw error;
}
}
Azure Durable Functions (Python)
import azure.functions as func
import azure.durable_functions as df
myApp = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
@myApp.route(route="orchestrators/{functionName}")
@myApp.durable_client_input(client_name="client")
async def http_start(req: func.HttpRequest, client):
instance_id = await client.start_new(req.route_params["functionName"], None, None)
return client.create_check_status_response(req, instance_id)
@myApp.orchestration_trigger(context_name="context")
def order_orchestrator(context: df.DurableOrchestrationContext):
order = context.get_input()
try:
yield context.call_activity("validate_order", order)
yield context.call_activity("check_inventory", order["items"])
yield context.call_activity("process_payment", order)
tasks = [
context.call_activity("send_email", order),
context.call_activity("update_analytics", order),
]
yield context.task_all(tasks)
yield context.call_activity("schedule_shipment", order)
return {"status": "completed"}
except Exception as e:
if "PaymentFailed" in str(e):
yield context.call_activity("release_inventory", order)
raise
Explicación
- Máquinas de estados: Step Functions modela workflows como máquinas de estados JSON. Cada estado es un paso (Task, Choice, Parallel, Wait, Pass) y las transiciones definen el flujo. AWS persiste la máquina de estados misma, así que si una Lambda supera el timeout, Step Functions la reintenta según la política configurada sin perder el contexto del workflow.
- Ejecución durable (Temporal): los workflows de Temporal son código, no JSON. Una función de workflow corre como un replay determinista de eventos; cada ejecución de actividad se registra como un evento. Si el worker se cae, otro worker reproduce el workflow desde el último evento registrado, saltando actividades ya completadas. Esto hace los workflows durables sin gestión de estado explícita.
- Fan-out / fan-in: las ramas paralelas (enviar email y actualizar analytics a la vez) reducen la duración total del workflow. En Step Functions, usa un estado
Parallel. En Temporal, usaPromise.all(). En Durable Functions, usatask.all(). El orquestador espera a que todas las ramas completen antes de continuar. - Eventos externos: los workflows pueden esperar señales externas. Un workflow puede pausarse en “Esperar aprobación manual” por horas o días. Step Functions soporta callbacks y tareas wait-for-callback. Temporal soporta señales — mensajes externos que un workflow puede esperar. Durable Functions soporta eventos externos vía
wait_for_external_event().
Variantes
| Orquestador | Formato | Vendor | Mejor para | Modelo de costo |
|---|---|---|---|---|
| Step Functions | JSON ASL | AWS | Nativo AWS, diseño visual | Por transición de estado |
| Temporal | Código (cualquier lenguaje) | Open-source | Lógica compleja, portabilidad | Self-hosted / Cloud |
| Durable Functions | Código (.NET/JS/Python) | Azure | Usuarios de Azure Functions | Ejecución + storage |
| Camunda | BPMN | Open-source | Modelado de procesos de negocio | Self-hosted / SaaS |
Lo que funciona
- Mantén las funciones Lambda stateless e idempotentes: el orquestador gestiona el estado; las funciones deben ser puras. Si una función se reintenta, debe producir el mismo resultado sin efectos secundarios.
- Usa backoff exponencial con jitter para reintentos: reintentar cada 1 segundo crea thundering herds. Agrega jitter aleatorio para dispersar los reintentos. Step Functions lo soporta de forma nativa; en Temporal, configura las políticas de reintento en las actividades.
- Configura timeouts en cada actividad: una espera ilimitada por una API externa puede estancar un workflow para siempre. Define timeouts por actividad (por ejemplo, 30 segundos para procesamiento de pago, 5 minutos para aprobación humana). Distingue entre errores reintentables (timeout, 5xx) y no reintentables (4xx, validación).
- Almacena payloads grandes en S3: Step Functions tiene un límite de payload de 256KB. Si tu workflow pasa archivos grandes o datasets, almacénalos en S3 y pasa URIs a través de la máquina de estados. Temporal tiene un límite gRPC de 2MB; usa blob storage de la misma forma para datos grandes.
- Monitorea métricas de ejecución de workflows: rastrea tasa de éxito, duración promedio, conteo de reintentos por actividad y costo por transición de estado.
Errores comunes
- Poner lógica de negocio en la máquina de estados: el JSON de Step Functions es para coordinación, no para reglas de negocio. Las condicionales complejas y transformaciones pertenecen a funciones Lambda. Una máquina de estados con docenas de estados
Choicees inmantenible — refactoriza la lógica a las funciones. - Pasar secretos a través del estado del workflow: el estado del workflow se registra en logs y es visible en la consola del orquestador. Nunca pases API keys, tokens o datos personales en payloads de estado. Pasa referencias (por ejemplo, el order ID) y deja que las funciones recuperen los secretos desde AWS Secrets Manager o Azure Key Vault.
- Ignorar idempotencia en reintentos de actividades: Step Functions puede reintentar una Lambda que tuvo éxito parcial (por ejemplo, cobró al cliente pero superó el timeout antes de retornar). El reintento cobra al cliente de nuevo. Diseña siempre las actividades para chequear claves de idempotencia antes de mutar estado.
- No versionar máquinas de estados: cambiar la definición de una máquina de estados en vivo afecta las ejecuciones en curso. Publica los cambios como versiones con alias y prueba con un porcentaje canary antes del rollout completo.
Avanzado: Saga Pattern con Compensación
Para workflows que abarcan varios servicios (pedido, inventario, pago, envío), el patrón saga coordina transacciones distribuidas con acciones compensatorias ante fallos:
// Temporal saga con compensación
export async function orderSaga(workflowId: string, order: Order): Promise<void> {
const compensations: (() => Promise<void>)[] = [];
try {
await Activities.reserveInventory(order);
compensations.push(() => Activities.releaseInventory(order));
await Activities.chargePayment(order);
compensations.push(() => Activities.refundPayment(order));
await Activities.scheduleShipment(order);
// Sin compensación para shipment — una vez enviado, está hecho
} catch (err) {
// Ejecuta compensaciones en orden inverso
for (const compensate of compensations.reverse()) {
try { await compensate(); }
catch (c) { console.error('Compensación falló:', c); }
}
throw err;
}
}
Cada paso registra una compensación antes de avanzar al siguiente. Si cualquier paso falla, las compensaciones se ejecutan en orden inverso. Este patrón funciona con los tres orquestadores: Step Functions usa bloques Catch con invocaciones de Lambda de compensación, Durable Functions usa try/catch con llamadas a actividades. Para un recorrido dedicado con workflows de rollback e idempotencia, consulta la receta del patrón Saga.
Troubleshooting
- Ejecución atascada en estado Running: una actividad está colgada o su heartbeat superó el timeout. Revisa el estado fallido en la consola de Step Functions (o la salida de
temporal workflow describe) y verifica el timeout y el intervalo de heartbeat de la actividad. - Picos de reintentos en transiciones de estado: una dependencia downstream es inestable. Revisa el conteo de reintentos por estado en CloudWatch; un bucle de reintentos caliente suele indicar una clave de idempotencia ausente en una operación de escritura.
- El payload supera el límite de 256KB: la ejecución falla con
States.DataLimitExceeded. Guarda el payload en S3 y pasa{ "bucket": ..., "key": ... }a través de la máquina de estados. - Un cambio en la máquina de estados rompe ejecuciones en curso: las ejecuciones largas conservan la definición con la que arrancaron. Publica los cambios como una versión nueva detrás de un alias y mueve el tráfico gradualmente.
- Un workflow Express llega al límite de 5 minutos: las ejecuciones Express se detienen a los 5 minutos. Mueve el trabajo de larga duración a un workflow Standard o divídelo en ejecuciones encadenadas.
Lectura Adicional
- Documentación de AWS Step Functions — tipos de estados, cuotas y precios de Express vs Standard.
- Documentación de Temporal — ejecución durable, señales y versionado de workflows.
- Documentación de Azure Durable Functions — patrones de orquestador y timers durables.
- Receta de workflows con Step Functions — deep dive exclusivo de AWS con map states y procesamiento por lotes.
Notas de Producción
- Publica los cambios de la máquina de estados como versiones: las ejecuciones en curso conservan la definición con la que arrancaron; mueve el alias solo después de que pase un porcentaje canary.
- Alerta sobre métricas a nivel de workflow: ejecuciones fallidas, duración p99 y transiciones de estado limitadas; las alertas de errores de Lambda por sí solas no detectan workflows atascados.
- Registra el ARN de ejecución en cada actividad: una sola línea de log debería permitir trazar una invocación de Lambda hasta su ID de ejecución de workflow.
- Prueba los caminos de compensación en staging: ejecuta las ramas de fallo trimestralmente, no solo el happy path; son las que corren cuando algo falla de verdad.
Puntos Clave
- Usa un orquestador cuando un workflow tenga más de dos pasos dependientes o necesite reintentos, esperas o aprobación humana.
- Step Functions encaja en workflows nativos de AWS con monitoreo visual; Temporal en lógica compleja y multi-cloud; Durable Functions en equipos de Azure Functions.
- Mantén las actividades stateless e idempotentes; el orquestador es dueño del estado, así que cada reintento debe ser seguro.
- Pasa referencias, no payloads: el estado del workflow se registra en logs y Step Functions limita la entrada a 256KB.
Errores Comunes en Producción
- Editar una máquina de estados en vivo en vez de publicar una versión nueva; las ejecuciones en curso siguen corriendo silenciosamente la definición anterior.
- Configurar timeouts de actividad más largos que la ventana de heartbeat de la tarea, de modo que el trabajo sano se mata por parecer irresponsivo.
- Pasar payloads completos entre estados y chocar con el límite de 256KB en producción; en desarrollo nunca pasó porque los pedidos de prueba eran pequeños.
- Reintentar una actividad de cobro o escritura no idempotente y facturar al cliente dos veces.
- Elegir Express para trabajo de larga duración (límite de 5 minutos) o Standard para trabajos cortos de alto volumen pagando por transición de estado.
- Probar solo el happy path; las ramas de compensación quedan sin probar hasta el día en que corren de verdad.
Preguntas frecuentes
¿Debería usar Step Functions o Temporal?
Step Functions es más simple para workflows centrados en AWS con monitoreo visual. Temporal es más capaz para lógica compleja, portabilidad multi-cloud y workflows de larga duración con código arbitrario. Si necesitas ejecutar workflows on-premise o multi-cloud, Temporal es la mejor opción.
¿Cómo manejo esperas largas (horas o días) en un workflow?
Usa estados de espera con callbacks. Step Functions soporta estados Wait con timestamps. Los workflows de Temporal pueden dormir por días sin que el worker corra activamente durante el sleep; el orquestador programa un evento de despertar. Durable Functions soporta timers durable.
¿Cuál es el costo de Step Functions?
Los workflows Standard cobran por transición de estado. Un workflow de 10 estados ejecutado 1,000,000 de veces cuesta ~$25. Los workflows Express cobran por ejecución y duración, optimizados para workflows de alto volumen y corta duración (menos de 5 minutos). Elige Express para alto throughput, Standard para workflows de larga duración.
¿Puedo probar workflows localmente?
Step Functions Local (Docker) corre máquinas de estados localmente. Temporal incluye un servidor de desarrollo local (temporal server start-dev). Durable Functions tiene un runtime local integrado con Azure Functions Core Tools. Prueba siempre los workflows localmente antes de desplegar.
¿Cómo implemento el patrón saga con compensación?
Registra una función de compensación antes de cada paso en el workflow. Si cualquier paso falla, ejecuta las compensaciones en orden inverso. En Temporal, usa un array de compensaciones e itera en reversa en el catch. En Step Functions, usa bloques Catch que invocan funciones Lambda de compensación. Cada compensación debe ser idempotente — puede ejecutarse dos veces.
¿Cómo versiono workflows sin romper ejecuciones en curso?
En Step Functions, usa versiones y aliases para desplegar nuevas definiciones sin afectar ejecuciones en curso. En Temporal, usa WorkflowVersion para ramificar la lógica según la versión con la que inició la ejecución. En Durable Functions, usa versionado de orquestación con IDurableOrchestrationContext. Prueba siempre las nuevas versiones con un porcentaje canary antes del rollout completo.
¿Cuál es la duración máxima de un workflow?
Step Functions Standard: hasta 1 año por ejecución. Express: hasta 5 minutos. Temporal: sin límite estricto; los workflows pueden correr indefinidamente. Durable Functions: hasta el período de retención de storage (7 días por defecto, configurable). Para workflows más largos que el límite de plataforma, usa checkpoints y reinicia el workflow desde el último checkpoint.
Recursos Relacionados
Minimizar la Latencia de Cold Start en Funciones Serverless
Cómo reducir tiempos de cold start en AWS Lambda, Azure Functions y Cloud Run usando concurrencia provisionada, lazy loading, tuning de runtime y optimización de dependencias.
RecipeDiseñar Sistemas Event-Driven con Event Buses y Brokers
Cómo diseñar servicios débilmente acoplados con eventos, event buses y message brokers: patrones pub/sub y event sourcing para comunicación asíncrona.
RecipeGestionar Transacciones Distribuidas con el Saga Pattern
Cómo implementar orquestación y coreografía de sagas para mantener consistencia de datos entre microservicios sin transacciones distribuidas ni two-phase commit.
RecipeDiseñar un API Gateway Escalable para Microservicios
Construí un gateway de API que enrute requests, maneje autenticación, rate limiting, caching y traducción de protocolos entre clientes y microservicios backend.
RecipeImplementar Event Sourcing en Arquitecturas Serverless
Cómo capturar todos los cambios como eventos inmutables con AWS Lambda, DynamoDB streams y event stores para audit trails y consultas temporales.
RecipeOrquestar Workflows Serverless con AWS Step Functions
Construir workflows de state machine con AWS Step Functions usando estados secuenciales, paralelos y map para orquestar funciones Lambda y procesos de larga duracion.