intermediate Por Mathias Paulenko

Arquitectura Serverless — Patrones y Anti-Patrones

Guía práctica de arquitectura serverless: diseño de funciones, cold starts, patrones event-driven, gestión de estado y errores comunes con AWS Lambda, Azure Functions y GCP Cloud Functions.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

La arquitectura serverless te permite ejecutar código sin aprovisionar ni gestionar servidores. El proveedor cloud se encarga de infraestructura, escalado y parcheo; tú proporcionas funciones que se ejecutan en respuesta a eventos. Aunque serverless elimina la gestión de servidores, introduce nuevas restricciones: límites de tiempo de ejecución, cold starts, falta de estado y debugging distribuido. A continuación: patrones que funcionan y anti-patrones que causan problemas.

Cuándo Usar

  • For alternatives, see Complete Guide to Serverless Architecture.

  • Tráfico variable o impredecible (pago por ejecución ahorra dinero)

  • Flujos de trabajo event-driven (subidas de archivo, cambios en BD, tareas programadas)

  • Microservicios con ciclos de despliegue independientes

  • Prototipos y MVPs donde la velocidad importa más que la optimización

  • Pipelines de procesamiento que pueden dividirse en pasos discretos

Patrones Principales

PatrónCaso de UsoEjemplo
Función por endpoint HTTPAPIs RESTAPI Gateway → Lambda
Función event-drivenProcesamiento asíncronoSubida S3 → Lambda generador de thumbnails
Función programadaJobs cronCloudWatch Events → Lambda de reporte nocturno
Función triggered por colaCargas de trabajo desacopladasSQS → Lambda procesador de órdenes
Función triggered por streamDatos en tiempo realDynamoDB Streams → Lambda actualizador de caché

Diseño de Funciones — Lo que funciona

# AWS Lambda handler — mantener inicialización fuera del handler para reutilizar
import boto3
import json

# Inicializado una vez por ciclo de vida del contenedor
s3_client = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ProcessedFiles')

def lambda_handler(event, context):
    # Handler se ejecuta en cada invocación
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    # Procesar el archivo
    response = s3_client.get_object(Bucket=bucket, Key=key)
    content = response['Body'].read()
    
    # Guardar metadata
    table.put_item(Item={
        'fileId': key,
        'bucket': bucket,
        'size': len(content),
        'processedAt': context.aws_request_id
    })
    
    return {'statusCode': 200, 'body': json.dumps({'processed': key})}
// Azure Function con trigger HTTP e inyección de dependencias
const { app } = require('@azure/functions');

class OrderService {
    async createOrder(orderData) {
        // Lógica de negocio aquí
        return { id: crypto.randomUUID(), ...orderData };
    }
}

app.http('createOrder', {
    methods: ['POST'],
    authLevel: 'anonymous',
    handler: async (request, context) => {
        const orderData = await request.json();
        const service = new OrderService();
        const order = await service.createOrder(orderData);
        return { jsonBody: order, status: 201 };
    }
});

Manejando Cold Starts

EstrategiaImpactoImplementación
Keep-alive (ping)Elimina cold startCron de CloudWatch cada 5 minutos
Concurrencia provisionadaInstancias precalentadasConcurrencia provisionada de Lambda
Minimizar dependenciasInicialización más rápidaEliminar paquetes no usados, tree-shake
Usar lenguajes compiladosInicio más rápidoGo, Rust, o compilación AOT de .NET
Pool de conexionesReutilizar conexiones a BDInicializar clientes globalmente

Gestión de Estado

Las funciones serverless son stateless. Persiste estado externamente:

# AWS Step Functions — orquesta flujos de trabajo con estado
Comment: Flujo de Procesamiento de Órdenes
StartAt: ValidateOrder
States:
  ValidateOrder:
    Type: Task
    Resource: arn:aws:lambda:...:validate-order
    Next: ProcessPayment
  ProcessPayment:
    Type: Task
    Resource: arn:aws:lambda:...:process-payment
    Catch:
      - ErrorEquals: ["PaymentFailed"]
        ResultPath: "$.error"
        Next: NotifyFailure
    Next: NotifySuccess

Errores Comunes

  • Lambda monolítica — poner toda una aplicación en una función; dividir en funciones de propósito único
  • Espera síncrona — llamar servicios lentos sincrónicamente dentro de una función; usar patrones async
  • Ignorar límites de timeout — Lambda tiene máximo 15 minutos; jobs largos necesitan ECS o Batch
  • Tratar funciones como servidores — almacenar estado en memoria o disco local
  • Sin estrategia de reintentos — fallas transitorias deben manejarse con colas de mensajes muertos
  • Memoria sobreaprovisionada — la memoria controla CPU; testear para encontrar el punto óptimo

Troubleshooting

  • High latency between services: trace the request path. Look for synchronous chains, missing caching, and oversized payloads that cross network boundaries.
  • Single point of failure: identify components without redundancy. Add replicas, failover, or circuit breakers before scaling traffic.
  • Unexpected coupling between services: review shared databases, libraries, and schemas. Bound contexts should own their data and expose stable interfaces.
  • Cost spikes after scaling: Reserved capacity or spot instances can reduce steady-state spend.
  • Difficult to reason about the system: maintain architecture decision records and service dependency maps.

Temas Avanzados

Escenario Detallado: Pipeline de Procesamiento de Imagenes

Arquitectura: S3 upload -> Lambda -> SQS -> Lambda -> DynamoDB
Plataforma: AWS
Volumen: 10k imagenes/dia

Paso 1: Subida de imagen
  Usuario sube imagen a S3 bucket: uploads/originals/
  Evento S3:ObjectCreated dispara Lambda resize-function

Paso 2: Lambda resize-function
  - Lee imagen desde S3
  - Genera 3 tamanos: thumbnail (100x100), medium (500x500), large (1200x1200)
  - Sube versiones a S3: uploads/resized/
  - Publica mensaje a SQS: image-processed-queue
    Mensaje: { "originalKey": "...", "thumbnailKey": "...", "mediumKey": "...", "largeKey": "..." }
  - Timeout: 30s (resize de imagen grande puede tomar 10-15s)
  - Memoria: 1024MB (necesario para procesamiento de imagen)

Paso 3: SQS -> Lambda metadata-function
  - Lee mensaje de SQS
  - Extrae metadata: dimensiones, formato, tamanho, hash
  - Almacena en DynamoDB tabla: ImageMetadata
    PK: imageId (uuid generado), SK: version (thumbnail|medium|large)
  - Elimina mensaje de SQS (procesamiento exitoso)
  - Si falla 3 veces -> DLQ: image-processed-dlq

Configuracion IaC (Terraform):
  resource "aws_lambda_function" "resize" {
    function_name = "image-resize"
    handler       = "handler.lambda_handler"
    runtime       = "python3.11"
    memory_size   = 1024
    timeout       = 30
    reserved_concurrent_executions = 50
    environment {
      variables = {
        OUTPUT_BUCKET = "uploads-resized"
        QUEUE_URL     = aws_sqs_queue.image_processed.id
      }
    }
  }

  resource "aws_sqs_queue" "image_processed" {
    name              = "image-processed-queue"
    visibility_timeout = 60  # > lambda timeout
    redrive_policy = jsonencode({
      deadLetterTargetArn = aws_sqs_queue.dlq.arn
      maxReceiveCount     = 3
    })
  }

Monitoreo:
  - CloudWatch Alarms: errores > 1%, duracion p95 > 20s
  - DLQ alert: mensaje en DLQ -> SNS -> Slack
  - X-Ray tracing para ver latencia por paso

Costo estimado (10k imagenes/dia):
  Lambda: ~$15/mes (1.5M invocaciones)
  S3: ~$2/mes (storage)
  SQS: ~$0.40/mes
  DynamoDB: ~$1.25/mes (on-demand)
  Total: ~$19/mes

Como manejo transacciones distribuidas en serverless?

No uses transacciones ACID distribuidas. Usa el patron saga: cada funcion hace su parte y publica un evento. Si un paso falla, una funcion compensatoria deshace el trabajo anterior. En AWS, Step Functions coordina las sagas con estados de compensacion. Alternativamente, usa el patron outbox: la funcion escribe en la BD y publica el evento en la misma transaccion. Un proceso separado lee el outbox y publica los eventos.

Como testeo funciones serverless localmente?

Usa SAM CLI para AWS Lambda: sam local invoke -e event.json. Para Azure, Azure Functions Core Tools: func start. Crea eventos de prueba en JSON que imiten los eventos reales (S3, SQS, API Gateway). Para testeo de integracion, usa LocalStack que emula servicios de AWS localmente. Para CI, ejecuta los tests en GitHub Actions con SAM CLI instalado. Manten los tests de handler separados de los tests de logica de negocio.

End of document. Review and update quarterly.

Notas de Producción

  • Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
  • Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
  • Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
  • Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.

Puntos Clave

  • Aplica arquitectura serverless — patrones y anti-patrones cuando necesites una solución práctica para tu caso de uso.
  • Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
  • Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
  • Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.

Errores Comunes en Producción

  • Tratar la guía como un checklist para completar una vez en lugar de una práctica por evolucionar.
  • Adoptar cada recomendación de golpe en lugar de comenzar con un cambio medido.
  • Saltar la evaluación de madurez e imponer prácticas avanzadas a un equipo no preparado.
  • No actualizar runbooks y expectativas de guardia al introducir nuevas prácticas.
  • Ignorar datos reales de incidentes al priorizar qué partes de la guía aplicar primero.
  • No asignar un responsable que revise decisiones trimestralmente.
  • Copiar ejemplos sin adaptarlos a las herramientas y restricciones reales del equipo.
  • Olvidar medir resultados antes de agregar la siguiente mejora.

Preguntas frecuentes

¿Cómo empiezo con esto en un proyecto existente?
Empieza con una parte pequeña y aislada de tu codebase. Aplica los conceptos de esta guía a un módulo o servicio. Mide el impacto, luego expande a otras áreas.
¿Qué herramientas necesito?
Las herramientas mencionadas throughout esta guía se listan en cada sección. La mayoría son open-source y ampliamente adoptadas. Consulta los recursos relacionados para instrucciones de setup.
¿Cómo mido el éxito después de implementar esto?
Define métricas claras antes de empezar: benchmarks de rendimiento, tasas de error o indicadores de mantenibilidad. Compara antes y después. Itera basándote en datos, no en suposiciones.