05 - Patrones Avanzados¶
Tabla de Contenidos¶
Microservicios¶
Estilo arquitectónico donde la aplicación se compone de servicios pequeños, independientes y desplegables de forma autónoma, cada uno ejecutándose en su propio proceso y comunicándose mediante mecanismos ligeros (HTTP/gRPC/mensajes).
Monolito vs Microservicios¶
graph TD
subgraph "Monolito"
M[Aplicación Única]
M --> MDB[(Base de Datos<br/>Compartida)]
M --- M1[Auth Module]
M --- M2[Orders Module]
M --- M3[Products Module]
M --- M4[Payments Module]
end
subgraph "Microservicios"
GW[API Gateway] --> A[Auth Service]
GW --> O[Order Service]
GW --> P[Product Service]
GW --> PAY[Payment Service]
A --> ADB[(Auth DB)]
O --> ODB[(Order DB)]
P --> PDB[(Product DB)]
PAY --> PAYDB[(Payment DB)]
O <-->|gRPC| P
O <-->|Events| PAY
end
Principios de Diseño¶
- Single Responsibility: Cada servicio hace una cosa bien
- Database per Service: Cada servicio tiene su propia base de datos (no comparten esquema)
- Decentralized Governance: Cada equipo elige su stack tecnológico
- Design for Failure: Asumir que las llamadas a otros servicios pueden fallar
- Smart Endpoints, Dumb Pipes: La lógica está en los servicios, no en el middleware
API Gateway¶
Punto de entrada único que maneja concerns transversales:
graph TD
C[Clientes] --> GW[API Gateway]
GW --> |Rate Limiting| GW
GW --> |Autenticación| GW
GW --> |Routing| GW
GW --> |Request Aggregation| GW
GW --> S1[Service A]
GW --> S2[Service B]
GW --> S3[Service C]
Funciones del API Gateway: - Autenticación y autorización centralizada - Rate limiting y throttling - Request routing y load balancing - Response aggregation (BFF - Backend for Frontend) - SSL termination - Logging y monitoring - Circuit breaking
Herramientas populares: Kong, AWS API Gateway, Nginx + Lua, Envoy, Traefik
Service Mesh¶
Capa de infraestructura dedicada a la comunicación service-to-service. Cada servicio tiene un proxy sidecar.
graph LR
subgraph "Service A Pod"
A[App A] <--> PA[Envoy Proxy<br/>Sidecar]
end
subgraph "Service B Pod"
B[App B] <--> PB[Envoy Proxy<br/>Sidecar]
end
PA <-->|mTLS, Retry,<br/>Circuit Break| PB
PA --> CP[Control Plane<br/>Istio / Linkerd]
PB --> CP
Resuelve: mTLS entre servicios, observabilidad (traces distribuidos), retry/timeout policies, canary deployments.
Desafíos de Microservicios¶
| Desafío | Solución |
|---|---|
| Transacciones distribuidas | Saga Pattern (coreografía u orquestación) |
| Consistencia de datos | Event sourcing + eventual consistency |
| Debugging distribuido | Distributed tracing (Jaeger, Zipkin, OpenTelemetry) |
| Service discovery | Consul, etcd, Kubernetes DNS |
| Config management | Consul KV, AWS Parameter Store, Vault |
| Despliegue complejo | CI/CD por servicio, Docker + Kubernetes |
| Testing | Contract testing (Pact), integration tests con containers |
Cuándo NO Usar Microservicios¶
- Equipos pequeños (< 5-8 devs)
- Dominio no bien comprendido (primero construye un monolito modular)
- Sin cultura DevOps madura
- Requisitos de latencia ultra baja (cada hop de red suma latencia)
Regla de oro: Empieza con un monolito modular. Extrae microservicios cuando el dolor de escalar lo justifique.
Serverless¶
Modelo de ejecución donde el cloud provider gestiona la infraestructura completamente. El código se ejecuta en funciones efímeras activadas por eventos.
Arquitectura Serverless Típica¶
graph LR
U[Usuario] --> APIGW[API Gateway]
APIGW --> L1[Lambda:<br/>createOrder]
APIGW --> L2[Lambda:<br/>getOrder]
L1 --> DDB[(DynamoDB)]
L1 --> SQS[SQS Queue]
SQS --> L3[Lambda:<br/>processPayment]
L3 --> SNS[SNS Topic]
SNS --> L4[Lambda:<br/>sendEmail]
L2 --> DDB
S3[S3: Upload imagen] --> L5[Lambda:<br/>resizeImage]
L5 --> S3OUT[S3: Thumbnails]
CRON[EventBridge:<br/>cada 5 min] --> L6[Lambda:<br/>cleanupExpired]
FaaS: Function as a Service¶
| Proveedor | Servicio | Runtime Max | Memoria Max | Concurrencia |
|---|---|---|---|---|
| AWS | Lambda | 15 min | 10 GB | 1000 (default) |
| Cloud Functions | 9 min (2nd gen) | 32 GB | Ilimitada | |
| Azure | Functions | Ilimitado (plan premium) | 14 GB | 200 (plan consumption) |
| Cloudflare | Workers | 30 seg (free) | 128 MB | Ilimitada |
Cold Start¶
Cuando una función se invoca por primera vez (o después de inactividad), el proveedor debe:
- Provisionar un contenedor
- Descargar el código
- Inicializar el runtime
- Ejecutar la función
graph LR
subgraph "Cold Start (500-3000ms)"
A[Provisionar Container] --> B[Cargar Código] --> C[Init Runtime] --> D[Ejecutar Handler]
end
subgraph "Warm Start (1-10ms)"
E[Ejecutar Handler]
end
Mitigación del cold start: - Provisioned concurrency (AWS): Mantener N instancias warm - Runtime liviano: Python/Node.js arrancan más rápido que Java/C# - Reducir dependencias: Menos código = menor tiempo de carga - SnapStart (AWS Lambda Java): Snapshot de la JVM inicializada
Ventajas y Desventajas¶
| Ventajas | Desventajas |
|---|---|
| Zero ops (no gestionar servidores) | Cold starts (latencia variable) |
| Pay per invocation (idle = $0) | Vendor lock-in |
| Auto-scaling automático | Límites de ejecución (15 min max) |
| Deployment simple (sube un ZIP) | Debugging/testing local difícil |
| Ideal para event-driven | Stateless obligatorio |
| Reducción de costos a baja escala | Costoso a alta escala constante |
Serverless vs Containers vs VMs¶
graph TD
subgraph "Espectro de Abstracción"
VM[VMs / Bare Metal<br/>Control total<br/>Más ops] --> CONT[Containers / K8s<br/>Control medio<br/>Ops medio]
CONT --> SL[Serverless / FaaS<br/>Sin control infra<br/>Zero ops]
end
| Criterio | Serverless | Containers | VMs |
|---|---|---|---|
| Startup time | Segundos (cold) | Segundos | Minutos |
| Costo en idle | $0 | Pago continuo | Pago continuo |
| Escala a cero | Sí | Con KEDA/Knative | No |
| Workloads largos | No (15 min max) | Sí | Sí |
| Personalización | Limitada | Alta | Total |
| Costo a escala | Puede ser alto | Eficiente | Eficiente |
Circuit Breaker¶
Patrón de estabilidad que previene que una falla en un servicio downstream se propague en cascada a todo el sistema. Inspirado en los circuit breakers eléctricos.
Estados¶
stateDiagram-v2
[*] --> Closed
Closed --> Open: Umbral de fallos<br/>alcanzado
Open --> HalfOpen: Timeout expira<br/>(cooldown period)
HalfOpen --> Closed: Request de prueba<br/>exitoso
HalfOpen --> Open: Request de prueba<br/>falla
note right of Closed
Requests fluyen normalmente.
Se cuentan los fallos.
end note
note right of Open
Requests rechazados inmediatamente.
Retorna fallback/error.
No se hace request al servicio.
end note
note right of HalfOpen
Se permite UN request de prueba.
Si éxito → Closed.
Si fallo → Open.
end note
Configuración Típica¶
failure_threshold: 5 # Fallos para abrir el circuito
success_threshold: 3 # Éxitos en half-open para cerrar
timeout: 30s # Tiempo en estado Open antes de probar
request_timeout: 2s # Timeout por request individual
monitoring_window: 60s # Ventana para contar fallos
Ejemplo Práctico¶
sequenceDiagram
participant OrderSvc
participant CB as Circuit Breaker
participant PaymentSvc
Note over CB: Estado: CLOSED
OrderSvc->>CB: cobrar()
CB->>PaymentSvc: POST /charge
PaymentSvc-->>CB: 200 OK
CB-->>OrderSvc: Cobro exitoso
OrderSvc->>CB: cobrar()
CB->>PaymentSvc: POST /charge
PaymentSvc-->>CB: 500 Error
Note over CB: Fallo 1/5
OrderSvc->>CB: cobrar()
CB->>PaymentSvc: POST /charge
PaymentSvc-->>CB: Timeout
Note over CB: Fallo 2/5
Note over CB: ... 3 fallos más ...
Note over CB: Estado: OPEN
OrderSvc->>CB: cobrar()
CB-->>OrderSvc: Error inmediato (sin llamar a Payment)
Note over CB: Retorna fallback
Note over CB: 30s después → HALF-OPEN
OrderSvc->>CB: cobrar()
CB->>PaymentSvc: POST /charge (prueba)
PaymentSvc-->>CB: 200 OK
Note over CB: Estado: CLOSED
Patrones Complementarios¶
1. Retry con Exponential Backoff¶
Intento 1: inmediato
Intento 2: esperar 1s
Intento 3: esperar 2s
Intento 4: esperar 4s
Intento 5: esperar 8s + jitter aleatorio
El jitter (aleatoriedad) evita que todos los clientes reintenten al mismo tiempo (thundering herd).
2. Bulkhead (Mamparo)¶
Aislar recursos para que un servicio fallido no consuma todos los threads/conexiones.
graph TD
subgraph "Sin Bulkhead"
TP1[Thread Pool: 100 threads]
TP1 --> SA[Service A: 90 threads ocupados - LENTO]
TP1 --> SB[Service B: 10 threads - Afectado]
end
subgraph "Con Bulkhead"
TP2[Pool A: 50 threads]
TP3[Pool B: 50 threads]
TP2 --> SA2[Service A: 50 threads - LENTO]
TP3 --> SB2[Service B: 50 threads - No afectado]
end
3. Fallback¶
Cuando el circuit breaker está abierto, proveer una respuesta degradada:
- Cache stale ("últimos datos conocidos")
- Valor por defecto
- Servicio alternativo
- Mensaje al usuario: "Servicio temporalmente no disponible"
Implementaciones¶
| Lenguaje/Framework | Librería |
|---|---|
| Java | Resilience4j, Hystrix (deprecated) |
| Node.js | opossum, cockatiel |
| Python | pybreaker, tenacity (retry) |
| Go | sony/gobreaker, hystrix-go |
| .NET | Polly |
CQRS¶
Command Query Responsibility Segregation. Separar los modelos de lectura y escritura de la aplicación.
El Problema¶
En una arquitectura tradicional, el mismo modelo y la misma base de datos se usan para leer y escribir. Esto genera tensión:
- Las lecturas necesitan desnormalización para ser rápidas (joins son costosos)
- Las escrituras necesitan normalización para garantizar integridad
- Las lecturas son generalmente mucho más frecuentes que las escrituras (ratio 100:1 es común)
Arquitectura CQRS¶
graph TD
subgraph "Commands (Escritura)"
CMD[Command: CreateOrder] --> CH[Command Handler]
CH --> WDB[(Write DB<br/>Normalizada<br/>PostgreSQL)]
end
WDB -->|Eventos de cambio<br/>CDC / Domain Events| SYNC[Sincronización]
subgraph "Queries (Lectura)"
Q[Query: GetOrderSummary] --> QH[Query Handler]
QH --> RDB[(Read DB<br/>Desnormalizada<br/>Elasticsearch / Redis)]
end
SYNC --> RDB
Niveles de CQRS¶
Nivel 1: Separación en Código¶
Misma DB, pero modelos separados para lectura y escritura en el código.
WriteModel: Order { userId, items[], status, shippingAddress }
ReadModel: OrderSummary { orderId, userName, totalItems, total, statusLabel }
Nivel 2: Bases de Datos Separadas¶
Write DB optimizada para escrituras (normalizada, ACID). Read DB optimizada para lecturas (desnormalizada, eventualmente consistente).
Nivel 3: CQRS + Event Sourcing¶
La write side almacena eventos. La read side se construye proyectando esos eventos.
graph LR
subgraph "Write Side"
CMD[Command] --> AGG[Aggregate]
AGG --> ES[(Event Store<br/>Append-only)]
end
ES -->|OrderCreated<br/>ItemAdded<br/>OrderShipped| PROJ[Projector]
subgraph "Read Side"
PROJ --> V1[(View: Order List<br/>SQL Table)]
PROJ --> V2[(View: Dashboard<br/>Redis Hash)]
PROJ --> V3[(View: Search<br/>Elasticsearch)]
end
Q1[Query: Lista] --> V1
Q2[Query: Dashboard] --> V2
Q3[Query: Buscar] --> V3
Eventual Consistency¶
En CQRS con DBs separadas, hay un delay entre una escritura y su reflejo en la read DB. Esto se llama replication lag o eventual consistency.
Estrategias para manejarlo:
- Optimistic UI: El frontend muestra el cambio inmediatamente sin esperar confirmación del read model
- Read-your-writes consistency: Después de escribir, leer directamente de la write DB (bypass temporal de la read DB)
- Polling / Webhooks: El cliente consulta hasta que el read model se actualice
- Causal consistency: Incluir un version/timestamp para que el cliente sepa si está viendo datos actualizados
Cuándo Usar CQRS¶
Sí: - Alta disparidad entre volumen de lecturas y escrituras - Modelos de lectura complejos (dashboards, reportes, búsquedas) - Necesidad de escalar lecturas y escrituras independientemente - Domain-driven design con aggregates complejos
No: - Aplicaciones CRUD simples - Dominio con lógica de negocio mínima - Equipos pequeños sin experiencia en eventual consistency - Cuando la consistencia inmediata es un requerimiento duro
CQRS en la Práctica: Stack Común¶
| Componente | Opciones |
|---|---|
| Write DB | PostgreSQL, MySQL |
| Event Bus | Kafka, RabbitMQ, EventBridge |
| CDC | Debezium (captura cambios del WAL de PG) |
| Read DB (búsqueda) | Elasticsearch, OpenSearch |
| Read DB (cache/views) | Redis, DynamoDB |
| Read DB (analytics) | ClickHouse, BigQuery |
Recursos Recomendados¶
- Libro: Building Microservices - Sam Newman (2da edición)
- Libro: Implementing Domain-Driven Design - Vaughn Vernon
- Libro: Release It! - Michael Nygard (Circuit Breaker, Bulkhead)
- Paper: "CQRS Documents" - Greg Young
- Blog: Martin Fowler - "Microservices", "CircuitBreaker", "CQRS"
- Video: GOTO Conference - "When To Use Microservices" - Sam Newman
- Curso: AWS Serverless Workshop (gratis en GitHub)
- Herramienta: Resilience4j docs (resilience4j.readme.io)