30 de septiembre de 2026 · 12 min
Arquitecturas serverless: patrones, orquestación y lo que nadie te cuenta del día dos
Serverless · AWS · Arquitectura
Serverless se vende como "olvídate de los servidores". La parte cierta: no aprovisionas, no parchas y no escalas máquinas. La parte que se omite: sigues teniendo sistemas distribuidos, con sus fallos parciales, reintentos, duplicados y límites; solo que ahora cada pieza es una caja administrada con reglas propias y tú ensamblas el conjunto. Esta es una guía para ensamblarlo bien: qué patrones funcionan, cómo se maneja el error en cada capa, cómo se prueba y observa, y qué estropea la factura o la fiabilidad.
Qué es serverless (con criterio)
Un servicio es serverless si reúne cuatro cosas:
- Sin servidores que administrar (ni instancias, ni parches, ni clústeres que dimensionar).
- Escala automáticamente con la demanda, incluyendo hasta cero.
- Pago por uso (solicitudes, tiempo de ejecución, datos), no por capacidad ociosa.
- Alta disponibilidad integrada.
En AWS: Lambda (cómputo), Fargate (contenedores), API Gateway y AppSync (APIs), DynamoDB (datos), S3 (objetos), SQS/SNS/EventBridge (mensajería y eventos), Step Functions (orquestación), Cognito (identidad), Aurora Serverless y Kinesis, entre otros. La arquitectura serverless típica tiene poco código propio y mucho pegamento entre servicios administrados. Esa proporción es una señal de salud: si tu "arquitectura serverless" es una Lambda enorme con un framework web dentro, probablemente solo cambiaste el lugar donde corre un monolito.
El cambio de mentalidad
| Antes (servidores) | Serverless | |---|---| | Capacidad = lo que aprovisionas | Capacidad = límites de cuenta y de cada servicio | | Un proceso de larga vida con estado en memoria | Ejecuciones efímeras y sin estado | | Escalas verticalmente o con un ASG | Escala por solicitud/evento | | Fallo = una máquina cae | Fallo = reintentos, duplicados, throttling | | Pago fijo por capacidad | Pago variable, ligado al uso | | Observas hosts | Observas solicitudes y eventos |
De ahí salen las reglas: idempotencia, estado externo, asincronía por defecto, límites explícitos.
Patrones que funcionan
1. API síncrona: la clásica
Cliente → API Gateway (autorización, límites) → Lambda → DynamoDB
Para APIs de lectura/escritura con latencia moderada. Reglas: respuestas rápidas (decenas o cientos de ms), operaciones largas movidas a asíncrono, una función por recurso o grupo de rutas cohesivo (ni una por ruta ni un monolito), y protección de la base con concurrencia reservada.
2. Procesamiento asíncrono con cola
Cliente → API → SQS → Lambda (lotes) → destino
└─ DLQ
La API valida y encola (202 Accepted); el trabajo ocurre después. La cola da contrapresión, reintentos y aislamiento de fallos. Es el patrón más rentable para cualquier trabajo que no necesite respuesta inmediata.
3. Orientado a eventos y fan-out
Fuente → EventBridge / SNS ─┬─ SQS → Lambda (facturación)
├─ SQS → Lambda (inventario)
└─ SQS → Firehose → S3 (analítica)
Quien emite el evento no conoce a los consumidores. Cada consumidor lleva su cola y DLQ. Cuidado con los ciclos (un consumidor que vuelve a publicar el evento que lo activa).
4. Procesamiento de archivos
S3 (subida directa por URL prefirmada) → evento → Lambda / Step Functions → S3 + DynamoDB
El cliente sube directo a S3, sin pasar por tu API. La creación del objeto dispara el procesamiento. Usa prefijos distintos para entrada y salida (para no activarte a ti mismo).
5. Flujos de trabajo con Step Functions
Cuando hay varios pasos con ramificaciones, esperas, reintentos y compensaciones, una máquina de estados es mejor que Lambdas encadenadas.
| | Standard | Express | |---|---|---| | Duración | Hasta 1 año | Hasta 5 minutos | | Semántica | Exactamente una vez por paso | Al menos una vez | | Volumen | Miles por segundo (menor) | Cientos de miles por segundo | | Costo | Por transición de estado | Por solicitudes y duración | | Historial | Detallado, consultable | Registros en CloudWatch | | Uso | Procesos de negocio largos, aprobaciones humanas | Alto volumen y corta duración (ingesta, transformaciones) |
Un fragmento de definición con reintentos y captura de errores:
{
"StartAt": "CobrarPago",
"States": {
"CobrarPago": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "cobrar-pago", "Payload.$": "$" },
"Retry": [
{ "ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2, "MaxAttempts": 4, "BackoffRate": 2.0, "JitterStrategy": "FULL" }
],
"Catch": [
{ "ErrorEquals": ["PagoRechazado"], "Next": "NotificarRechazo" },
{ "ErrorEquals": ["States.ALL"], "Next": "CompensarYFallar" }
],
"Next": "ReservarInventario"
},
"ReservarInventario": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "reservar-inventario", "Payload.$": "$" }, "End": true },
"NotificarRechazo": { "Type": "Fail", "Error": "PagoRechazado" },
"CompensarYFallar": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "reembolsar", "Payload.$": "$" }, "Next": "Fallo" },
"Fallo": { "Type": "Fail" }
}
}
Step Functions también se integra directamente con más de 200 servicios de AWS (DynamoDB, SQS, SNS, ECS, Glue...) sin Lambda de por medio, reduciendo código y costo. Y soporta el patrón saga: si un paso falla, ejecutas acciones de compensación de los anteriores. Orquestación frente a coreografía. Con orquestación (Step Functions) un coordinador explícito decide el orden: visible, fácil de depurar, con más acoplamiento al coordinador. Con coreografía (eventos) cada servicio reacciona por su cuenta: desacoplado, pero el flujo completo queda implícito y cuesta seguirlo. Para procesos de negocio con pasos y compensaciones, orquesta; para notificar cambios a quien le interese, coreografía.
6. Tuberías de datos
Aplicaciones → Kinesis / Firehose → S3 → Glue / Athena → paneles
Ingesta con Kinesis o Firehose, almacenamiento en S3 en formato columnar (Parquet) y consulta con Athena. Sin clústeres que mantener y pagando por datos escaneados.
7. Aplicaciones de tiempo real
AppSync (GraphQL con suscripciones) o API Gateway WebSocket + Lambda + DynamoDB para conexiones y mensajes.
Manejo de errores: capa por capa
La mayor diferencia entre un sistema serverless fiable y uno frágil es saber quién reintenta, cuántas veces y qué pasa después.
| Capa | Quién reintenta | Dónde terminan los fallos |
|---|---|---|
| API Gateway → Lambda (síncrono) | El cliente | Respuesta de error al cliente |
| Lambda asíncrona (S3, SNS, EventBridge) | Lambda (2 reintentos por defecto) | Destino de fallo / DLQ de la función |
| SQS → Lambda | La cola (por visibilidad), hasta maxReceiveCount | DLQ de la cola |
| Streams → Lambda | Lambda, reintentando hasta que pase o expire | Destino en caso de fallo; riesgo de bloquear el shard |
| EventBridge → destino | EventBridge (política de reintentos, hasta 24 h) | DLQ del destino |
| Step Functions | Reglas Retry y Catch del estado | Estados de compensación / Fail |
Principios comunes:
- Idempotencia en todo consumidor. Todo puede llegar más de una vez.
- Una DLQ con alarma y dueño en cada punto asíncrono.
- Reintentos con espera exponencial y jitter, y límites claros.
- Distingue errores transitorios de permanentes: reintenta los primeros; los segundos, a la DLQ de inmediato.
- Procedimiento de redrive documentado: sin él, la DLQ es un cementerio.
Estado y datos
Las funciones son efímeras: el estado vive fuera.
- DynamoDB para acceso por clave y escala predecible (diseñada por patrones de acceso).
- Aurora Serverless v2 / RDS + RDS Proxy cuando necesitas SQL; cuida las conexiones con Lambda.
- S3 para objetos y archivos.
- ElastiCache / MemoryDB (no serverless en el mismo sentido, aunque hay variantes) para caché de baja latencia.
- Parameter Store / Secrets Manager / AppConfig para configuración y secretos.
Evita guardar estado en /tmp o en variables globales como si fueran persistentes.
Seguridad en pocas líneas
- Un rol por función, con permisos mínimos y acotados por recurso.
- Autoriza en el borde y en el backend.
- Secretos en Secrets Manager, no en variables de entorno.
- Cifrado en reposo y en tránsito en todos los servicios.
- Valida entradas de eventos como si fueran externas: un evento de S3 o SQS puede contener datos controlados por un atacante.
- Dependencias: las funciones heredan las vulnerabilidades de tus paquetes; escanea y actualiza.
- Cuentas separadas por entorno y despliegues desde una canalización, no desde portátiles.
Pruebas: la parte difícil
Probar serverless es más difícil porque mucho del comportamiento está en la configuración y la integración (permisos, formatos de eventos, límites), no en el código.
| Nivel | Qué cubre | Cómo |
|---|---|---|
| Unitarias | Lógica de negocio pura | Funciones desacopladas de los servicios (separa la lógica del handler) |
| Integración local limitada | Forma de los eventos | Eventos de ejemplo, sam local (útil pero con diferencias) |
| Integración en la nube | Permisos, configuración, límites reales | Desplegar un entorno efímero por rama y ejecutar pruebas contra los servicios reales |
| De extremo a extremo | Flujos completos | Pocas, sobre el entorno real; verifica resultados observables |
| De carga y resiliencia | Concurrencia, throttling, fallos | Pruebas dirigidas; inyecta fallos en las dependencias |
Recomendación: menos emulación, más pruebas contra la nube, con entornos baratos y desechables. Los emuladores locales nunca reproducen del todo permisos, cuotas ni consistencia.
Observabilidad
- Un identificador de correlación que viaja en eventos, mensajes y logs.
- Logs JSON con retención.
- Trazas con X-Ray u OpenTelemetry a través de API Gateway, Lambda, SQS y DynamoDB.
- Métricas por flujo de negocio, no solo por función: pedidos completados, tiempo de extremo a extremo.
- Alarmas sobre síntomas: errores 5XX, antigüedad de colas, DLQ, throttles,
IteratorAge. - Paneles que muestren el recorrido de un flujo, no una lista de funciones.
Costos: modelo mental y números
El costo de una arquitectura serverless es la suma de muchas partidas pequeñas. Un ejemplo ilustrativo de un backend de API con 3 millones de solicitudes al mes, 150 ms promedio por función, 512 MB, con DynamoDB bajo demanda (precios de referencia; verifícalos):
| Partida | Cálculo aproximado | |---|---| | API Gateway (HTTP API) | 3 M × ~1 USD/M ≈ 3 USD | | Lambda | 3 M solicitudes y 3 M × 0.15 s × 0.5 GB = 225 000 GB-s: dentro de la capa gratuita o céntimos–pocos dólares | | DynamoDB (bajo demanda) | Unos pocos millones de lecturas/escrituras: pocos dólares | | CloudWatch Logs | Depende del volumen de logs; a menudo la mayor partida | | Total | Típicamente decenas de dólares como mucho a este volumen |
A este tamaño, serverless es muy barato. La pregunta de costo aparece a gran escala y con carga estable: con cientos de millones de solicitudes constantes, contenedores o instancias bien dimensionadas y comprometidas pueden salir más baratos. Haz la cuenta con tu volumen, no con la intuición. Costos que sorprenden:
- Logs excesivos o con retención infinita.
- NAT Gateway si metes funciones en una VPC.
- Step Functions Standard con muchas transiciones por ejecución.
- Bucles accidentales que se llaman a sí mismos.
- Reintentos que multiplican invocaciones ante un error persistente.
- Concurrencia aprovisionada olvidada.
- Transferencia de datos entre regiones y hacia internet.
Controles: presupuestos con alertas, etiquetas de costos por flujo, límites de concurrencia, y revisiones mensuales de las partidas principales.
Anti-patrones
| Anti-patrón | Por qué falla | En su lugar |
|---|---|---|
| Lambda llama a Lambda de forma síncrona | Doble facturación mientras esperan, latencia acumulada, fallos en cascada | Cola, evento o Step Functions |
| Función monolítica con un framework web dentro | Arranque lento, permisos amplios, despliegues grandes | Funciones cohesivas por responsabilidad (o un monolito modular si el equipo lo prefiere, con conocimiento de sus costos) |
| Sondeo (polling) desde una función programada para "esperar" algo | Costo y latencia | Eventos, SQS, Step Functions con espera |
| Compartir una base relacional sin proxy con cientos de funciones | Agotan conexiones | RDS Proxy, límites de concurrencia, o DynamoDB |
| Procesos largos en la ruta síncrona | Timeouts | 202 + procesamiento asíncrono + consulta de estado |
| Sin idempotencia | Duplicados en producción | Claves de idempotencia |
| Ciclos de eventos | Costos desbocados | Prefijos/filtros distintos, alarmas de presupuesto |
| Todo en una cuenta, desplegado a mano | Riesgo y deriva | Cuentas por entorno, IaC, CI/CD |
| Ignorar los límites (concurrencia, tamaño, tiempo) | Fallos bajo carga | Conocerlos y pruebas de carga |
Cuándo serverless no es la mejor opción
- Carga alta y constante donde el cálculo favorece a contenedores o instancias comprometidas.
- Procesos de larga duración o con estado en memoria (más de 15 minutos por unidad de trabajo, sesiones persistentes).
- Latencias que no toleran ni el p99 de un arranque en frío y no compensa la concurrencia aprovisionada.
- Dependencia de software con requisitos especiales (GPU, licencias por máquina, kernel específico).
- Equipos sin experiencia en sistemas distribuidos asíncronos y sin tiempo para aprenderlos.
- Requisitos de portabilidad entre nubes muy estrictos.
No es una religión: muchas arquitecturas sólidas mezclan (API en contenedores, eventos y procesamiento en Lambda, datos en DynamoDB y S3).
Por qué me gusta este enfoque
Mucho de lo que construyo en serverless empezó como soluciones para facilitarme la vida: automatizaciones, integraciones, pequeñas herramientas que no quería volver a hacer a mano. Con el tiempo descubrí que la misma idea sirve en las empresas: cuanto más fácil es entender un proceso, más fácil es mantenerlo. Un flujo con eventos claros, una cola con su DLQ y un par de funciones pequeñas se explica en una pizarra; un sistema donde todo depende de un servidor que "solo conoce una persona" no. El mejor ejemplo propio son los runners de CI/CD efímeros sobre contenedores sin servidores: menos costo, menos mantenimiento y más libertad para desplegar. Esa solución resume lo que busco con este estilo de arquitectura.
Una forma sensata de empezar
- Elige un flujo acotado y de bajo riesgo (un procesamiento de archivos, una integración, un endpoint nuevo).
- Defínelo como código (SAM, CDK o Terraform), con un entorno efímero por rama.
- Pon desde el inicio: DLQ, alarmas, logs estructurados, trazas, idempotencia y un presupuesto.
- Mide latencia, errores y costo reales durante unas semanas.
- Decide con datos si ampliar el enfoque o no.
- Para sistemas existentes, aplica el patrón estrangulador: extrae funcionalidades una a una detrás de la misma API.
Para finalizar
Serverless cambia dónde se acumula la complejidad: menos en operar máquinas, más en entender eventos, reintentos, límites y configuración. Cuando respetas esas reglas —idempotencia, estado externo, asincronía, DLQ con dueño, observabilidad desde el día uno—, entregas rápido con poca operación. Cuando no, descubres el "día dos": duplicados, atascos silenciosos y facturas que nadie puede explicar. La buena noticia es que casi todo eso se previene en el diseño. La pregunta no es "¿serverless sí o no?", sino "¿qué piezas de mi sistema se benefician de no tener servidores, y cuáles no?".