Arquitectura de Software: De 0 a Arquitecto de Sistemas Empresariales
Guía completa sobre arquitectura de software empresarial. Patrones, C4, microservicios, B2B, multi-tenant, casos reales, antipatrones y mejores prácticas. Enfocado en negocio y decisiones estratégicas.
Arquitectura de Software: De 0 a Arquitecto de Sistemas Empresariales
Imagina que eres un ingeniero civil. Construyes una casa, un edificio de 10 pisos y una metrópolis son tres cosas completamente distintas. No es lo mismo dibujar los planos de una casa que diseñar cómo deben conectarse las tuberías, eléctricas y sistemas estructurales de una ciudad de millones de personas.
Lo mismo ocurre en software.
Una aplicación web de un solo servidor no requiere el mismo pensamiento arquitectónico que un sistema que debe procesar millones de órdenes de compra por minuto en tiempo real, servir a 5,000 clientes empresariales simultáneamente, garantizar cero errores de transacción y escalar desde 50 a 500,000 usuarios sin que nadie se dé cuenta.
Esta es la guía que necesitaba cuando comencé mi carrera. La que te mostrará no solo cómo diseñar sistemas, sino por qué se diseñan de cierta forma, cuándo aplicar cada patrón, cuándo es overkill y cuándo es insuficiente.
Iremos paso a paso. Desde lo más simple (una aplicación de tres capas) hasta sistemas que se despliegan en múltiples continentes y procesan miles de millones de transacciones al año.
Parte I: Fundamentos - Entendiendo el Juego
Sección 1: El Desafío Arquitectónico - ¿Por Qué Importa la Arquitectura?
Hace años, recibí un mensaje de un cliente desesperado:
“Nuestro sistema de inventario se cae cada viernes a las 2 PM. Tenemos 200 órdenes en cola. No sabemos qué está pasando. El código se ve bien.”
Cuando revisé el sistema, encontré algo fascinante. No era el código el problema. El sistema estaba bien escrito. El problema era la arquitectura.
La aplicación era un monolito gigante. Cuando alguien en Marketing hacía un reporte de ventas, ese reporte consultaba la misma base de datos donde se estaban procesando órdenes en tiempo real. Ambos competían por el mismo conexión de base de datos. Ambos querían el mismo CPU. Cuando el reporte se ejecutaba, todo lo demás se congelaba.
Eso es un problema arquitectónico, no un problema de código.
La arquitectura de software responde a tres preguntas fundamentales:
1. ¿Cómo organizamos el código? 2. ¿Cómo hacemos que el sistema crezca sin colapsar? 3. ¿Cómo evitamos que un cambio pequeño derribe todo?
Si no respondes estas preguntas durante el diseño, tendrás que responderlas a las 3 AM mientras 10,000 usuarios no pueden hacer sus compras.
El Cubo de Complejidad Arquitectónica
Hay tres dimensiones de complejidad en todo sistema:
┌─────────────────────────────────────┐
│ ESCALA (¿Cuántos usuarios?) │
│ COMPLEJIDAD (¿Cuántas features?) │
│ CONFIABILIDAD (¿Qué tan crítico?) │
└─────────────────────────────────────┘
Un sitio web personal para tus fotos:
- Escala: 10-100 usuarios
- Complejidad: Baja (mostrar fotos, comentarios)
- Confiabilidad: Baja (si se cae 1 hora, no importa)
Un sistema de banca en línea:
- Escala: Millones de usuarios
- Complejidad: Alta (transferencias, pagos, reportes, fraude)
- Confiabilidad: Crítica (un error = dinero perdido)
Aquí está el secreto: la arquitectura es directamente proporcional a qué tan alto esté cada una de estas dimensiones.
Si tu aplicación está arriba en las tres dimensiones, necesitas un arquitecto. Si está abajo en las tres, un junior con 6 meses puede manejarlo.
Sección 2: Los Cuatro Pilares de la Arquitectura de Software
Cualquier decisión arquitectónica se basa en estos cuatro pilares. Todos los patrones, todas las discusiones, todo gira alrededor de estos:
1. Escalabilidad - Crecer sin Dolor
El negocio es simple: tu startup crece de 100 a 1 millón de usuarios en un año. ¿Qué pasa?
Escalabilidad Vertical (Scale Up): Compras servidores más poderosos.
graph LR
A["100 usuarios<br/>1 servidor<br/>32GB RAM"] -->|Crece 10x| B["1,000 usuarios<br/>1 servidor<br/>128GB RAM"] -->|Crece 10x| C["10,000 usuarios<br/>PROBLEMA:<br/>No hay servidor<br/>con 1,280GB RAM"]
El problema: existe un límite físico. Los servidores más potentes del mundo no existen. O cuestan $5 millones.
Escalabilidad Horizontal (Scale Out): Compras más servidores mediocres.
graph LR
A["100 usuarios<br/>1 servidor"] -->|Crece 10x| B["1,000 usuarios<br/>10 servidores"] -->|Crece 10x| C["10,000 usuarios<br/>100 servidores"] -->|Crece 10x| D["100,000 usuarios<br/>1,000 servidores"]
¿Por qué importa? Porque escala infinitamente. Agregas servidores baratos ($50/mes cada uno) en lugar de servidores exóticos. Es la diferencia entre crecer y estar limitado.
La mayoría de unicornios (startups de $1B+) usan escalabilidad horizontal porque necesitan crecer 10-100x en años.
2. Confiabilidad - El Costo Oculto del Uptime
Aquí es donde la arquitectura toca dinero de verdad.
graph TB
A["99% Uptime<br/>(Startup)"] -->|"$500k/año"| B["99.9% Uptime<br/>(Escala media)"]
B -->|"$2M/año"| C["99.99% Uptime<br/>(Fintech, Pagos)"]
C -->|"$10M+/año"| D["99.999% Uptime<br/>(NASA, Banks)"]
style A fill:#90EE90
style B fill:#FFD700
style C fill:#FF8C00
style D fill:#FF4500
¿Qué significa cada uno en términos de negocio?
- 99% (Startup): Tu sistema se cae 3.6 horas al año. Tolerable si eres pequeño. Pierdes clientes pero no sufres consecuencias legales.
- 99.9% (Escala media): 21 minutos al año. Requiere redundancia (2 servidores). Si uno se cae, el otro toma.
- 99.99% (Fintech): 2 minutos al año. Requiere data centers múltiples, replicación geo-distribuida, failover automático. Un error = pérdida de dinero.
- 99.999%: Casi imposible. Ves esto en telecomunicaciones, aviación. Requiere equipos dedicados solo a confiabilidad.
La razón es simple: si Stripe se cae 1 hora, pierde millones en transacciones. Si tu startup se cae 1 hora, pierdes unos pocos clientes. Arquitecturas completamente diferentes.
Hay un concepto útil en ingeniería: Error Budget. Si garantizas 99.9%, tienes 21 minutos de “permiso” para cosas malas al año. ¿Qué haces con eso?
- 15 minutos en un deployment riesgoso
- 6 minutos en experimentos
- 0 minutos restantes
Este presupuesto dirige todas las decisiones arquitectónicas.
3. Mantenibilidad - Entender el Código en 6 Meses
He visto código que trabajé hace 6 meses y no lo entiendo. Es caos. Es imposible agregar features sin romper todo.
La arquitectura debe permitir que:
- Un nuevo desarrollador entienda el sistema en pocas semanas
- Cambiar una feature no requiera editar 50 archivos
- Los equipos pueden trabajar independientemente
4. Costo - El Rey Silencioso Que Elige Todo
Aquí viene la verdad incómoda: la arquitectura no la elige un arquitecto. La elige el presupuesto.
graph TB
Budget["¿Cuánto dinero tienes?"]
Budget -->|"<$500k/año"| Small["Startup"]
Budget -->|"$500k - $5M/año"| Medium["Escala Media"]
Budget -->|">$5M/año"| Large["Empresa Grande"]
Small --> SmallArch["Monolito<br/>1 servidor<br/>PostgreSQL"]
Medium --> MediumArch["Monolito Modular<br/>5 servidores<br/>Redis Cache"]
Large --> LargeArch["Microservicios<br/>50+ servidores<br/>Kubernetes"]
style SmallArch fill:#FFCCCC
style MediumArch fill:#FFFFCC
style LargeArch fill:#CCFFCC
¿Por qué? Porque cada arquitectura tiene un costo operacional:
| Arquitectura | Servidores | DevOps? | Personas | Costo Anual |
|---|---|---|---|---|
| Monolito | 1 | No | 2 engineers | $250k |
| Monolito + Cache | 5 | Básico | 3 engineers | $500k |
| Microservicios | 50 | Sí (Kubernetes) | 10+ | $2M+ |
El trade-off de arquitectura es realmente un trade-off de costo.
Cuando alguien dice “usemos microservicios,” lo que está diciendo es “gastemos $2M en infraestructura.” Si no tienes ese presupuesto, es game over.
Aquí está la verdad pragmática: Un arquitecto senior no elige “la mejor arquitectura.” Elige la mejor arquitectura dentro del presupuesto real.
Un monolito bien construido cuesta menos, es más rápido de desarrollar, y es más fácil de debugguear. Microservicios escalan mejor, pero cuestan 10x más. Si tu presupuesto es $500k, el costo gana. Siempre.
Sección 3: Metodología C4 - Dibujando Arquitecturas que Se Entienden
C4 es una notación simple para dibujar arquitecturas. Tiene 4 niveles de zoom. Como Google Maps: zoomas out ves continentes, zoomas in ves calles.
C1 - Sistema Completo
Es la pregunta más alta: ¿Qué es este sistema?
Imagina un sistema de logística. En C1 ves:
graph TB
Users["👥 Usuarios<br/>(Conductores, Gerentes)"]
System["📦 Sistema de Logística<br/>(Ordenes, Rutas, Inventario)"]
Externals["🔗 Sistemas Externos<br/>(Google Maps, Stripe)"]
Users -->|Usa| System
System -->|Integra| Externals
Eso es C1. Nada de detalles. Solo: “Aquí hay un sistema que hace esto.”
C2 - Contenedores
Zoomas un poco. Ves las grandes piezas. ¿De qué está hecho?
graph TB
subgraph Usuarios["Usuarios"]
Drivers["Aplicación Móvil<br/>(iOS/Android)"]
Managers["Portal Web<br/>(Dashboard)"]
end
subgraph Backend["Backend"]
API["API REST<br/>(Node.js)"]
Workers["Procesadores<br/>(Bull Queue)"]
end
subgraph Data["Datos"]
DB["PostgreSQL<br/>(Órdenes, Rutas)"]
Cache["Redis<br/>(Cache, Sessions)"]
end
Drivers -->|HTTP/REST| API
Managers -->|HTTP/REST| API
API -->|Lee/Escribe| DB
API -->|Lee/Escribe| Cache
Workers -->|Procesa| DB
External["Servicios Externos<br/>(Google Maps, Stripe)"]
API -->|Calls| External
Aquí empiezas a ver estructura. “La aplicación móvil habla con una API REST, que accede a una base de datos.”
C3 - Componentes
Zoomas más. Dentro de una caja (un contenedor), ves sus componentes internos.
Miremos dentro del Backend. ¿Qué hay adentro de esa API REST?
graph TB
subgraph API["API REST"]
Auth["Componente Auth<br/>(JWT, Roles)"]
Orders["Componente Orders<br/>(CRUD, Business Logic)"]
Shipping["Componente Shipping<br/>(Rutas, Optimización)"]
Payments["Componente Payments<br/>(Stripe Integration)"]
Events["Componente Events<br/>(Event Bus)"]
end
subgraph External["Externos"]
DB["PostgreSQL"]
Cache["Redis"]
Maps["Google Maps API"]
end
Auth -->|Valida| Orders
Orders -->|Publica| Events
Shipping -->|Consulta| Maps
Payments -->|Registra| Events
Auth -->|Lee/Escribe| DB
Orders -->|Lee/Escribe| DB
Cache -->|Cache| Orders
Ahora ves cómo se comunican las partes internas.
C4 - Código
El último nivel. Ya dentro de un componente, ves las clases, métodos.
// Componente Orders
interface OrderService {
createOrder(dto: CreateOrderDTO): Promise<Order>;
updateOrderStatus(id: string, status: Status): Promise<void>;
getOrder(id: string): Promise<Order>;
}
class OrderServiceImpl implements OrderService {
constructor(
private db: DatabaseConnection,
private eventBus: EventBus,
private shippingService: ShippingService,
) {}
async createOrder(dto: CreateOrderDTO): Promise<Order> {
// Validación
if (!dto.items.length) throw new Error("No items");
// Crear orden
const order = await this.db.orders.insert({
customerId: dto.customerId,
items: dto.items,
status: "PENDING",
});
// Publicar evento
await this.eventBus.publish(new OrderCreatedEvent(order));
// Notificar shipping
await this.shippingService.schedulePickup(order.id);
return order;
}
}
Los cuatro niveles en conjunto forman una visión completa: desde “qué es el sistema” hasta “qué hace este método.”
Este es el primer superpoder de un arquitecto: poder comunicar ideas complejas en diferentes niveles de detalle.
Sección 3.5: La Matriz de Decisión - Conectando Negocio con Arquitectura
Antes de saltar a patrones, necesitas entender cómo el negocio dicta la arquitectura.
graph TB
Pregunta["¿Cuáles son tus restricciones?"]
Pregunta --> P1["¿Cuántos usuarios?"]
Pregunta --> P2["¿Cuánto presupuesto?"]
Pregunta --> P3["¿Criticidad?"]
Pregunta --> P4["¿Cuántos equipos?"]
P1 -->|"<100k"| Small["Arquitectura Simple"]
P1 -->|"100k - 10M"| Medium["Arquitectura Escalable"]
P1 -->|">10M"| Large["Arquitectura Distribuida"]
P2 -->|"<$500k"| Budget1["Monolito"]
P2 -->|"$500k - $5M"| Budget2["Modular"]
P2 -->|">$5M"| Budget3["Microservicios"]
P3 -->|"Baja (startup)"| Crit1["99% ok"]
P3 -->|"Media (escala)"| Crit2["99.9% requerido"]
P3 -->|"Alta (fintech)"| Crit3["99.99% mandatorio"]
P4 -->|"<5"| Team1["Un equipo<br/>Un monolito"]
P4 -->|"5-20"| Team2["Equipos separados<br/>Módulos independientes"]
P4 -->|">20"| Team3["Múltiples equipos<br/>Servicios independientes"]
Small --> Decision1["Monolito simple<br/>1 BD, 1 servidor"]
Medium --> Decision2["Monolito robusto<br/>Replicación, Cache"]
Large --> Decision3["Microservicios<br/>Distribuido globalmente"]
La arquitectura no es una decisión técnica pura. Es una decisión de negocio que tiene implicaciones técnicas.
Déjame darte ejemplos reales de cómo esto funciona:
Caso 1: Startup SaaS (100 usuarios, $50k presupuesto)
- Presupuesto: Muy limitado
- Usuarios: Pocos
- Criticidad: Si se cae, algunos usuarios molestos pero nada catastrófico
- Equipos: 2 personas
┌─────────────────────┐
│ Decisión: Monolito │
│ ✓ Rápido │
│ ✓ Barato │
│ ✓ 2 personas lo │
│ manejan │
│ ✗ No escala a 1M │
│ pero no importa │
│ aún │
└─────────────────────┘
Caso 2: Marketplace (1M usuarios, $2M presupuesto)
- Presupuesto: Moderado
- Usuarios: Muchos y creciendo
- Criticidad: Si se cae, pierdo ventas (dinero real)
- Equipos: 15 personas
┌───────────────────────────┐
│ Decisión: Monolito │
│ Modular + Algunos │
│ Microservicios │
│ ✓ Core monolito (rápido) │
│ ✓ Payments = microservice │
│ (criticidad alta) │
│ ✓ Equipos independientes │
│ ✓ Escalable parcialmente │
└───────────────────────────┘
Caso 3: Fintech Global (100M usuarios, $50M presupuesto)
- Presupuesto: Ilimitado
- Usuarios: Masivos y distribuidos globalmente
- Criticidad: Un error = dinero perdido, demandas, regulación
- Equipos: 200+ personas
┌─────────────────────────┐
│ Decisión: Microservicios │
│ Distribuido Globalmente │
│ ✓ Cada región su │
│ instancia │
│ ✓ Servicios especializados
│ ✓ Equipos independientes │
│ ✓ Fallos aislados │
│ ✓ Compliance por país │
└─────────────────────────┘
La lección: No hay “mejor” arquitectura. Hay la arquitectura correcta para TUS restricciones.
Resumen de la Parte I
Hemos establecido los fundamentos:
- La arquitectura importa porque responde cómo organizas, escalas y mantienes sistemas
- Hay cuatro pilares: Escalabilidad, Confiabilidad, Mantenibilidad, Costo
- C4 es tu herramienta para comunicar arquitectura en diferentes niveles
- Las restricciones dictan la arquitectura, no al revés
Parte II: Patrones Arquitectónicos - Las Grandes Decisiones
Sección 4: Monolito vs Microservicios - La Guerra Eterna
Quiero que olvides todo lo que has escuchado sobre microservicios.
Olvida que alguien te dijo “los microservicios son el futuro” o “el monolito es obsoleto.” Ambos son herramientas. Un martillo no es mejor que un destornillador. Depende de qué estés construyendo.
El Monolito: Simple y Potente
Un monolito es todo en un lugar: un servidor, una base de datos, una aplicación.
graph LR
A["Usuarios"]
B["Un Servidor<br/>(Todo el código)"]
C["Una BD<br/>(Todos los datos)"]
A -->|HTTP| B
B -->|SQL| C
¿Cuándo un monolito es ORO PURO?
Si tienes <$500k presupuesto y <100k usuarios, un monolito es la decisión correcta. Aquí por qué:
- Velocidad: Features en producción en días
- Costo: 1 servidor + 1 BD = barato
- ACID garantizado: Un cliente compra → dinero se debita → inventario se reduce en SIMULTÁNEO
- Debugging: Un stacktrace cuenta toda la historia
- Equipos pequeños: 2-5 personas entienden todo
¿Cuándo un monolito se vuelve un problema?
graph TB
A["Monolito Creciente"]
A --> B1["Despliegues tardan 2+ horas"]
A --> B2["Merge conflicts cada mañana"]
A --> B3["Un cambio rompe 3 cosas"]
A --> B4["50+ personas esperando código"]
B1 -->|SI TIENES ESTOS| C["REFACTORIZA YA"]
B2 -->|SI TIENES ESTOS| C
B3 -->|SI TIENES ESTOS| C
B4 -->|SI TIENES ESTOS| C
style C fill:#FF6B6B
Si ninguno de estos síntomas existe, no rompas lo que funciona.
Los Microservicios: Necesarios Pero Caros
Microservicios = 20 aplicaciones independientes, cada una con BD, equipo, deploy.
graph TB
Usuarios["Usuarios"]
subgraph Services["20 Microservicios"]
S1["Orders"]
S2["Shipping"]
S3["Payments"]
S4["Auth"]
end
Usuarios --> S1
S1 -->|Mensaje| EventBus["Event Bus<br/>(Kafka)"]
EventBus --> S2
EventBus --> S3
¿Cuándo valen la pena?
Solo cuando tienes DINERO y NECESIDAD:
| Problema | Valor |
|---|---|
| Equipo > 50 personas | Demasiados conflictos de código |
| Usuarios > 10M | Un servidor NO puede procesar eso |
| Presupuesto > $5M | Puedes pagar DevOps expertos |
| Servicios críticos distintos | Pagos (99.99%) vs Reportes (99%) |
| Necesidad de escalar partes | Solo pagos es lento, ¿por qué escalar todo? |
Netflix (250M usuarios) = microservicios. Notion (100M usuarios) = monolito. Ambos ganan dinero. La diferencia: Netflix tienes $billions en ingresos, Notion tiene modelo más eficiente.
Realidad incómoda: Microservicios cuesta 5-10x más. El dinero elige.
El Secreto: Monolito Modular (El Camino Ganador)
Aquí está lo que nadie dice: las empresas exitosas usan un HÍBRIDO.
graph TB
subgraph Monolito["Una Aplicación (Una BD)"]
M1["Módulo Orders"]
M2["Módulo Shipping"]
M3["Módulo Auth"]
end
subgraph Micro["Pero algunos servicios son independientes"]
P1["Payments<br/>(criticidad ultra alta)"]
P2["Analytics<br/>(puede estar lento)"]
end
M1 -.->|API clara| M2
M1 -->|HTTP| P1
M1 -->|Async| P2
style Monolito fill:#CCFFCC
style Micro fill:#FFFFCC
Shopify hace esto. Stripe hace esto. Es la arquitectura pragmática ganadora.
Ventajas:
- Simplicidad del monolito para lo que funciona
- Flexibilidad de microservicios donde la necesitas
- Costo controlado
- Evoluciona con tu negocio
La Tabla de Decisión REAL
graph TB
Users["¿Cuántos<br/>Usuarios?"]
Users -->|"<100k"| A1["Monolito"]
Users -->|"100k-10M"| A2["Monolito<br/>Modular"]
Users -->|">10M"| A3["Híbrido"]
A1 -->|"<$500k"| R1["✓ Monolito<br/>simple"]
A1 -->|">$500k"| R1b["✓ Monolito<br/>robusto"]
A2 -->|"$500k-$2M"| R2["✓ Monolito<br/>Modular"]
A2 -->|">$2M"| R2b["✓ O microservicios"]
A3 -->|">$5M"| R3["✓ Híbrido<br/>inteligente"]
A3 -->|"<$5M"| R3b["✗ Espera,<br/>baja presupuesto"]
La lección de oro: Empieza simple. Solo refactoriza cuando sientas el dolor REAL, no el teórico.
graph TB
A["Usuarios"]
subgraph Servicios["Microservicios"]
S1["Servicio Orders<br/>(Node.js)"]
S2["Servicio Shipping<br/>(Go)"]
S3["Servicio Payments<br/>(Python)"]
S4["Servicio Auth<br/>(Rust)"]
end
subgraph Datos["Datos"]
D1["Orders DB<br/>(PostgreSQL)"]
D2["Shipping DB<br/>(MongoDB)"]
D3["Payments DB<br/>(PostgreSQL)"]
end
A -->|HTTP| S1
A -->|HTTP| S2
A -->|HTTP| S3
S1 -->|HTTP| S2
S1 -->|HTTP| S3
S1 -->|Auth| S4
S1 -.->|SQL| D1
S2 -.->|NoSQL| D2
S3 -.->|SQL| D3
Cada equipo tiene su propio servicio, su propia base de datos, su propio lenguaje, su propio ciclo de deploy.
Ventajas de Microservicios:
- Escalabilidad de equipos - 100 personas en 20 servicios = menos conflictos
- Flexibilidad tecnológica - El equipo de pagos usa Rust, el de shipping usa Go
- Despliegues independientes - Cambio el servicio de órdenes sin tocar pagos
- Escalabilidad selectiva - Si ordenes es lento, duplico solo ese servicio
- Resiliencia - Si shipping se cae, órdenes sigue funcionando
- Evolución constante - Puedo cambiar un servicio completo sin afectar otros
Desventajas de Microservicios:
- Complejidad distribuida - Ahora tienes 20 aplicaciones fallando de formas nuevas
- Latencia de red - Llamar otra aplicación es 100x más lento que una función local
- Transacciones distribuidas - Garantizar que múltiples servicios se sincronizen es pesadilla
- Monitoreo - Un usuario reporta “sistema lento” ¿De cuál de los 20 servicios es culpa?
- Debugging - Un request pasa por 10 servicios. ¿Dónde está el problema?
- Infraestructura - Kubernetes, load balancing, circuit breakers. Necesitas DevOps serios
La Tabla de Decisión
Hay un patrón en qué empresas grandes usan qué:
| Empresa | Usuarios | Empleados | Arquitectura | Razón |
|---|---|---|---|---|
| Netflix | 250M | 5000 | Microservicios | Escala masiva, múltiples lenguajes, deploy independiente |
| Airbnb | 100M | 4000 | Monolito + Servicios | Empezaron monolito, ahora algunos servicios críticos independientes |
| Stripe | 50M txn/día | 2000 | Monolito + Servicios | Backend monolito (ACID), servicios específicos para escala |
| Shopify | 1.7M negocios | 9000 | Microservicios | Múltiples regiones, tecnologías especializadas |
| Notion | 100M | 2000 | Monolito | Simplicidad, equipo más pequeño |
| GitHub | 100M usuarios | 3000 | Monolito + Servicios | Ruby on Rails principal + servicios de CI/CD |
¿Ves el patrón? Todas empezaron con monolito.
Shopify, Netflix, Amazon: todas un monolito. Luego, cuando tuvieron miles de millones de dólares, problemas de escala y cientos de ingeniero, pasaron a microservicios.
La Pregunta Correcta No Es “¿Monolito o Microservicios?”
Es: “¿Cuándo es el monolito tan grande que cuesta más mantenerlo que refactorizarlo?”
La respuesta es: cuando tienes estos síntomas:
- Despliegues toman horas - Porque cada pequeño cambio requiere testear TODO
- Equipos esperan en conflictos de git - Más de 5 personas editando el mismo archivo
- Un crash de una feature afecta otras - El servicio de búsqueda se cae y reportes también
- No puedes cambiar la BD sin afectar 20 cosas - El modelo de datos está acoplado
Si NINGUNO de estos síntomas existe, tu monolito es oro puro. Es simple, es rápido, es barato.
Si TODOS existen, es hora de empezar a extraer servicios.
El Enfoque Correcto: Modular Monolith
Hay un intermedio que muchos ignoran: Monolito Modular.
graph TB
subgraph Monolito["Una Aplicación<br/>(Un Servidor, Una BD)"]
M1["Módulo Orders<br/>(Interfaz clara)"]
M2["Módulo Shipping<br/>(Interfaz clara)"]
M3["Módulo Payments<br/>(Interfaz clara)"]
M4["Módulo Auth<br/>(Interfaz clara)"]
end
M1 -->|API Interna| M2
M1 -->|API Interna| M3
M1 -->|API Interna| M4
Es un monolito, pero está organizado como si fuera microservicios. Cada módulo:
- Tiene su propia carpeta
- Define una interfaz clara
- No accede directamente a otros módulos
- Podría ser extraído a un microservicio después
Empresas como Shopify usan esto. Les permite:
- La rapidez del monolito (una BD, ACID)
- La flexibilidad de microservicios (módulos independientes)
- Migrar servicios cuando sea necesario sin rediseñar todo
Ejemplo Práctico: Sistema de E-commerce
Año 1 - Monolito:
ecommerce/
├── users/
│ ├── service.ts
│ ├── controller.ts
│ └── repository.ts
├── products/
├── orders/
├── payments/
└── database.ts (una conexión)
Año 3 - 50 ingenieros, problemas de escala:
Extractamos Payments a un microservicio porque:
- Pagos necesita latencia baja (Stripe es lento)
- Pagos es crítico (un error = dinero perdido)
- El equipo de pagos quiere deployar 10 veces al día
- Órdenes necesita escalar sin escalar pagos
Pero Orders, Shipping, Users siguen en el monolito porque:
- Las transacciones entre ellos son frecuentes
- ACID es crítico
- El costo de distribuirlos es mayor que sus problemas
Año 5 - 200 ingenieros, múltiples regiones:
Ahora extractamos:
- Pagos (microservicio en USA)
- Shipping (microservicio, necesita escalas independientemente)
- Search (microservicio, usa Elasticsearch)
- Auth (microservicio compartido)
Pero Orders, Users, Inventory siguen acoplados porque son el corazón del negocio.
Resumen: Cuándo cada uno
Elige Monolito si:
- Equipo < 20 personas
- Características < 50 distintas
- Escala < 1 millón usuarios
- Presupuesto < $500k/año
Elige Monolito Modular si:
- Planeas crecer
- Tienes múltiples equipos
- Algunos módulos escalan diferente
Elige Microservicios si:
- Equipo > 100 personas
- Características > 500
- Escala > 100 millones usuarios
- Presupuesto > $5M/año
- Ya tienes DevOps experientado
En la próxima sección exploraremos el patrón BFF (Backend for Frontend), crucial cuando tienes múltiples clientes (web, móvil, smart TV) accediendo al mismo backend.
Sección 5: BFF Pattern - Backend for Frontend
El problema es clásico: Tu servicio de pagos tiene 5 clientes diferentes:
- App móvil (ancho de banda limitado, batería limitada)
- App web (más datos, UI compleja)
- Dashboard ejecutivo (necesita reportes complejos)
- Integraciones API (JSON puro, requisitos estrictos)
- Smart TV (pantalla pequeña, datos minimales)
Todos consultando el MISMO backend.
graph TB
subgraph Clients["Clientes Diferentes"]
Mobile["📱 App Móvil<br/>(Datos comprimidos)"]
Web["💻 Web<br/>(HTML, Datos complejos)"]
Executive["📊 Dashboard Ejecutivo<br/>(Reportes, Analytics)"]
API["🔗 API Partners<br/>(JSON puro)"]
TV["📺 Smart TV<br/>(Datos mínimos)"]
end
subgraph Problem["❌ PROBLEMA: UN SOLO BACKEND"]
Backend["API REST Única<br/>(Confusión total)"]
end
Mobile --> Backend
Web --> Backend
Executive --> Backend
API --> Backend
TV --> Backend
Backend -->|"¿Qué datos<br/>devuelvo?"| Confusion["El código se<br/>vuelve loco"]
El código termina como un nido de ratas:
// Lógica if/if/if/if para cada cliente
if (isMobile) {
/*datos comprimidos*/
}
if (isWeb) {
/*datos complejos*/
}
if (isExecutive) {
/*reportes*/
}
// Cambiar un cliente = afecta a todos
BFF Solution: Cada cliente su propio backend
graph TB
subgraph Clients["Clientes"]
Mobile["📱 Móvil"]
Web["💻 Web"]
Executive["📊 Ejecutivo"]
end
subgraph BFFs["Backends Especializados (BFF)"]
BFFMobile["BFF Mobile<br/>(Optimizado para<br/>móvil)"]
BFFWeb["BFF Web<br/>(Optimizado para<br/>web)"]
BFFExec["BFF Ejecutivo<br/>(Optimizado para<br/>reportes)"]
end
subgraph Core["Backend Core"]
Orders["Servicio Orders<br/>(Lógica de negocio)"]
Reports["Servicio Reports<br/>(Cálculos)"]
end
Mobile --> BFFMobile
Web --> BFFWeb
Executive --> BFFExec
BFFMobile -->|gRPC| Orders
BFFWeb -->|gRPC| Orders
BFFExec -->|gRPC| Reports
¿Por qué es mejor?
| Aspecto | Sin BFF | Con BFF |
|---|---|---|
| Cambiar app móvil | Afecta web y ejecutivo | Solo afecta BFF Mobile |
| Ancho de banda móvil | Datos complejos = lento | Datos mínimos = rápido |
| Dashboard ejecutivo | Reportes lentos en endpoint general | Reportes optimizados en BFF Exec |
| Código limpio | Spaghetti con if/else | Cada BFF responsable de su cliente |
| Escalabilidad | Problemas de móvil afectan a web | Se escalan independientemente |
Ejemplo de diferencia:
Endpoint /orders SIN BFF:
- Móvil necesita 10KB
- Devolvemos 500KB (web + reportes)
- Móvil usa 5% del ancho de banda
- Paga más en datos celulares
Endpoint /orders CON BFF Mobile:
- Devolvemos 10KB (exactamente lo necesario)
- Móvil usa 100% de lo que recibe
- Más rápido, menos datos, mejor UX
Cuándo usar BFF:
- Tienes 3+ clientes fundamentalmente diferentes
- Requisitos de datos distintos (móvil = mínimo, ejecutivo = máximo)
- Equipos independientes (equipo móvil, equipo web)
Cuándo NO usarlo:
- Tienes solo web
- Todos los clientes necesitan los mismos datos
- Escala selectiva - Si el dashboard está lento, escalo solo ese BFF
- Tecnologías diferentes - BFF móvil en Node.js, BFF ejecutivo en Go
- UI al backend - BFF web puede hacer SSR, BFF mobile puede cachear datos
Desventajas de BFF:
- Duplicación - El mismo endpoint existe en múltiples BFFs
- Complejidad - Más aplicaciones que mantener
- Sincronización - Si el core cambia, todos los BFFs deben cambiar
BFF es perfecto cuando:
- Tienes 3+ clientes distintos
- Tienen necesidades radicalmente diferentes
- Equipos independientes (equipo mobile, equipo web)
BFF es overkill cuando:
- Solo tienes web
- Todos los clientes necesitan los mismos datos
Sección 6: Event-Driven Architecture - Comunicación Desacoplada
Volvamos al problema del viernes a las 2 PM. Ahora estamos en microservicios.
El servicio de Orders crea órdenes. El servicio de Inventory maneja stock. ¿Cómo hablan sin acoplarse?
graph TB
A["Problema:<br/>¿Cómo se comunican<br/>Órdenes e Inventory?"]
A -->|Opción 1| B1["Llamadas Directas<br/>(Orders → Inventory)"]
A -->|Opción 2| B2["Eventos<br/>(Orders publica,<br/>Inventory escucha)"]
B1 -->|"Si Inventory<br/>es lento"| C1["❌ Orders se<br/>congela<br/>Acopladas"]
B2 -->|"Si Inventory<br/>es lento"| C2["✅ Orders sigue<br/>funcionando<br/>Desacopladas"]
style C1 fill:#FFB6B6
style C2 fill:#B6E4B6
Opción 1: Sincronía (Llamadas directas)
Orders dice: “Oye Inventory, reserva stock para esta orden.”
Inventory responde: “Ok, stock reservado” o “No hay stock, fallaste.”
Problema: si Inventory tarda 10 segundos en responder, Orders espera 10 segundos. Si Inventory cae, todo falla.
Opción 2: Asincronía (Eventos)
Orders dice: “Se creó una orden” y listo. No espera respuesta.
Inventory escucha el evento “OrderCreated”, reserva stock, y publica “StockReserved”.
Shipping escucha “StockReserved” y programa el envío.
graph LR
A["Orders publica:<br/>OrderCreated"]
B["Event Bus<br/>(Kafka, RabbitMQ)"]
A --> B
B -->|Escucha| C["Inventory"]
B -->|Escucha| D["Shipping"]
B -->|Escucha| E["Payments"]
B -->|Escucha| F["Analytics"]
C -->|Publica| B
D -->|Publica| B
E -->|Publica| B
F -->|Publica| B
¿Por qué importa esto en el negocio?
| Escenario | Sincronía (Llamadas) | Asincronía (Eventos) |
|---|---|---|
| Inventory se cae | Orders se cae, cliente no puede comprar | Orders sigue funcionando, ordenes en cola |
| Inventory está lento | Todos los requests lentos | Solo inventory lento, otros rápidos |
| Agregar nuevo servicio | Modificar Orders, agregar lógica | Nuevo servicio escucha sin cambiar nada |
| Crecimiento exponencial | Cambio masivo en código | Agrogo nodos que escuchan |
Uber? Event-driven. Toda orden genera eventos. Netflix? Event-driven. Cada view, cambio, fallo es un evento.
Event Sourcing: El siguiente nivel
En lugar de guardar solo el estado actual:
Orden: status=SHIPPED
Guardas todo lo que pasó:
1. OrderCreated (usuario hace click en comprar)
2. PaymentProcessed (tarjeta aprobada)
3. InventoryReserved (stock disponible)
4. ShippingScheduled (empaquetado)
5. OrderShipped (salió del almacén)
Ahora tienes un registro COMPLETO. Si algo falla, puedes recrear el estado desde los eventos.
Parte III: Casos de Uso Empresariales - Del Teórico al Real
Sección 7: B2B SaaS - Servicio Empresarial Multi-Cliente
Imagina que eres Slack. 500,000 empresas usan tu producto. Cada una con:
- Diferentes planes (free, pro, enterprise)
- Diferentes permisos
- Datos completamente aislados
- Requisitos de compliance distintos (HIPAA, GDPR, SOC2)
Arquitectura C2 - Contenedores principales:
graph TB
subgraph Clients["Clientes"]
Client1["Empresa A"]
Client2["Empresa B"]
Client3["Empresa C"]
end
subgraph Platform["Plataforma SaaS"]
Web["Web App"]
Mobile["Mobile App"]
API["API REST"]
end
subgraph Core["Core Services"]
Auth["Auth Service<br/>(Identificar cliente)"]
Workspace["Workspace Service<br/>(Datos del cliente)"]
Messages["Messages Service<br/>(Mensajes)"]
Files["Files Service<br/>(Documentos)"]
end
subgraph Data["Data Layer"]
DB["PostgreSQL<br/>(Separado por tenant)"]
Cache["Redis<br/>(Por tenant)"]
S3["S3<br/>(Archivos)"]
end
Client1 -->|Login| Web
Client2 -->|Login| API
Client3 -->|Login| Mobile
Web -->|auth_token| Auth
Auth -->|Qué datos del cliente| Workspace
Workspace -->|Lee datos| DB
Sección 7.1: Multi-Tenancy - Aislar Datos de 500,000 Empresas
La pregunta crítica: ¿Cómo aíslo los datos de cada cliente?
Hay tres enfoques:
1. Database per Tenant (Máxima seguridad)
Empresa A: base_datos_a
Empresa B: base_datos_b
Empresa C: base_datos_c
Ventajas:
- Completa aislación
- Fácil compliance (backups por cliente)
- Puedo elegir diferente BD para cada cliente
Desventajas:
- Pesadilla operacional (500,000 bases de datos)
- Costos altos
- Migrations son complejas
Quién lo usa: Salesforce, algunos SaaS enterprise.
2. Schema per Tenant (Balance)
postgres://
├── empresa_a_schema
├── empresa_b_schema
└── empresa_c_schema
Una base de datos, pero cada cliente tiene su schema.
Ventajas:
- Mejor que DB per tenant
- Buena seguridad
- Fácil backup y disaster recovery
Desventajas:
- Migrations afectan todos los schemas
- Query planing es más complejo
Quién lo usa: Heroku, Fly.io.
3. Row-Level Security (RLS)
-- Una tabla, datos de todos los clientes
CREATE TABLE messages (
id SERIAL,
tenant_id UUID,
content TEXT,
created_at TIMESTAMP
);
-- Política: Solo ves tus datos
CREATE POLICY tenant_isolation ON messages
USING (tenant_id = auth.jwt_claims()->>'tenant_id');
Una tabla para todos. PostgreSQL garantiza que solo ves tus datos.
Ventajas:
- Simplicidad operacional
- Costos bajos
- Escalabilidad máxima
Desventajas:
- Require que hagas bien la seguridad (un error = breach)
- Menos control por cliente
Quién lo usa: Vercel, Supabase, AWS RDS.
Recomendación: Usa RLS. Es simple y escala. Si necesitas compliance especial (HIPAA), luego migas a schema per tenant.
Sección 7.2: Autenticación Multi-Tenant
Cuando un usuario hace login, debes saber:
- ¿Quién es el usuario?
- ¿De qué tenant?
- ¿Qué permisos tiene?
// Middleware de autenticación
async function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "No token" });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// decoded = {
// userId: "user_123",
// tenantId: "company_456",
// email: "engineer@company.com",
// role: "ADMIN"
// }
// Guardar en el request
req.user = decoded;
next();
}
// Ahora en tus rutas
app.get("/messages", authMiddleware, async (req, res) => {
const messages = await db
.query("SELECT * FROM messages WHERE tenant_id = $1", [req.user.tenantId])
.limit(50);
return res.json(messages);
});
El JWT token contiene el tenant_id. Así cada request sabe a qué cliente pertenece.
Sección 8: Sistema de Logística en Tiempo Real
Vamos a un caso concreto: un sistema de logística que necesita:
- Procesar 10,000 órdenes por hora
- Mostrar ubicación de envíos en tiempo real (GPS)
- Calcular rutas óptimas (¿cuál es el orden de entregas?)
- Manejar choques sin derrocar el sistema
Arquitectura C3 - Componentes principales:
graph TB
subgraph Clients["Clientes"]
Driver["App Conductor<br/>(Ubicación en tiempo real)"]
Manager["Dashboard Gerente<br/>(Órdenes + Mapas)"]
Customer["App Cliente<br/>(Dónde está mi paquete)"]
end
subgraph Frontend["Frontend Layer"]
Web["Portal Web"]
Mobile["App Móvil"]
end
subgraph BFF["Backend for Frontend"]
BFF_Driver["BFF Driver"]
BFF_Manager["BFF Manager"]
BFF_Customer["BFF Customer"]
end
subgraph Services["Servicios Core"]
Orders["Servicio Orders"]
Routing["Servicio Routing<br/>(Rutas óptimas)"]
Tracking["Servicio Tracking<br/>(GPS real-time)"]
Notifications["Servicio Notificaciones"]
end
subgraph RealTime["Comunicación en Tiempo Real"]
WebSocket["WebSocket Server"]
Pub["Redis Pub/Sub"]
end
subgraph Data["Persistencia"]
DB["PostgreSQL<br/>(Órdenes, Rutas)"]
TimeSeries["TimescaleDB<br/>(GPS, Histórico)"]
Cache["Redis<br/>(Cache + Pub/Sub)"]
end
Driver -->|Envía GPS cada 5s| BFF_Driver
Manager -->|Consulta estado| BFF_Manager
Customer -->|¿Dónde está?| BFF_Customer
BFF_Driver -->|Publica evento| Pub
BFF_Manager -->|Escucha cambios| WebSocket
Tracking -->|Suscrito a GPS| Pub
Tracking -->|Almacena| TimeSeries
Orders -->|Crea orden| DB
Routing -->|Calcula ruta| Orders
Sección 8.1: El Desafío del GPS en Tiempo Real
Un conductor envía su ubicación cada 5 segundos. Con 1,000 conductores, son 12,000 updates por minuto.
Si haces esto:
// ❌ MAL - Escribir cada punto GPS en la BD
app.post("/driver/location", async (req, res) => {
const { driverId, lat, lng } = req.body;
// 12,000 escrituras por minuto en la BD = CUELLO DE BOTELLA
await db.query("INSERT INTO gps_points VALUES ($1, $2, $3)", [
driverId,
lat,
lng,
]);
res.json({ ok: true });
});
Crash garantizado.
Solución correcta:
// ✅ BIEN - Usar Redis Pub/Sub para broadcast en tiempo real
const redis = require("redis");
const publisher = redis.createClient();
app.post("/driver/location", async (req, res) => {
const { driverId, lat, lng, timestamp } = req.body;
// Publicar a todos los que escuchan este conductor
await publisher.publish(
`driver:${driverId}:location`,
JSON.stringify({
lat,
lng,
timestamp,
}),
);
// Guardar en cache (muy rápido)
await redis.set(
`driver:${driverId}:last_location`,
JSON.stringify({ lat, lng, timestamp }),
"EX",
300,
);
res.json({ ok: true });
});
// En el dashboard del gerente, WebSocket escucha
io.on("connection", (socket) => {
socket.on("subscribe_driver", (driverId) => {
const subscriber = redis.createClient();
subscriber.subscribe(`driver:${driverId}:location`);
subscriber.on("message", (channel, message) => {
socket.emit("driver_moved", JSON.parse(message));
});
});
});
Ahora:
- GPS llega a Redis (ultra rápido)
- Se broadcast a todos via WebSocket (tiempo real)
- Se guarda en TimescaleDB cada 1 minuto (para análisis históricos)
Sección 8.2: Rutas Óptimas - El Problema del Viajante de Comercio
Un conductor tiene 50 entregas en la ciudad. ¿En qué orden?
Si calcula todas las posibilidades: 50! combinaciones. Es imposible.
Necesitas algoritmos:
// Algoritmo heurístico: Nearest Neighbor
function calculateRoute(deliveries, startPoint) {
const route = [startPoint];
const remaining = [...deliveries];
while (remaining.length > 0) {
const current = route[route.length - 1];
// Encuentra el más cercano
const nearest = remaining.reduce((best, delivery) => {
const distance = haversineDistance(current, delivery.location);
return distance < best.distance ? { delivery, distance } : best;
});
route.push(nearest.delivery);
remaining.splice(remaining.indexOf(nearest.delivery), 1);
}
return route;
}
No es perfecto, pero es rápido (O(n²) en lugar de O(n!)).
Para mejor optimization, usas:
- Algoritmos genéticos: Crear “poblaciones” de rutas y evolucionar
- Simulated Annealing: Cambios pequeños y aleatorios hasta mejorar
- Machine Learning: Entrenar un modelo con rutas históricas
Empresas como Google Maps usan combinaciones de todos.
Sección 8.3: Manejo de Fallos
¿Qué pasa si un conductor no entrega? ¿La app se cae?
graph TB
A["Orden lista para entregar"]
B{Conductor disponible?}
C["Asignar a conductor"]
D["Enviar notificación"}
E{¿Entrega exitosa?}
F["Marcar como entregada"]
G["Error: Reintentar o escalar"]
H["Guardar en Dead Letter Queue"]
A --> B
B -->|Sí| C
B -->|No| H
C --> D
D --> E
E -->|Sí| F
E -->|No| G
G -->|Reintento disponible| C
G -->|No hay reintentos| H
Si algo falla, entra a una Dead Letter Queue. Luego:
- Retry automático cada 5 minutos
- Notificación al manager después de 3 reintentos fallidos
- Escala a un gerente de operaciones si falla completamente
Sección 9: ERP Modular - Empresa con Múltiples Departamentos
Un ERP (Enterprise Resource Planning) es lo opuesto a un SaaS simple. Una sola empresa con múltiples departamentos (Ventas, Finanzas, Producción, RH, etc.) que necesitan compartir datos.
Ejemplo: Cuando se crea una orden de venta (Ventas), automáticamente se crea:
- Una orden de producción (Producción)
- Un asiento contable (Finanzas)
- Un plan de envío (Logística)
Arquitectura C2:
graph TB
subgraph Departamentos["Departamentos"]
Ventas["Ventas<br/>(Órdenes, CRM)"]
Produccion["Producción<br/>(Manufactura)"]
Finanzas["Finanzas<br/>(Contabilidad)"]
Logistica["Logística<br/>(Envíos)"]
RH["RH<br/>(Nómina)"]
end
subgraph Core["Core ERP"]
Master["Master Data Service<br/>(Clientes, Productos, Precios)"]
Event["Event Bus<br/>(Órdenes, Cambios)"]
end
subgraph Data["Datos Compartidos"]
DB["PostgreSQL<br/>(Mismo esquema)"]
Warehouse["Data Warehouse<br/>(Analytics)"]
end
Ventas -->|Lee clientes| Master
Ventas -->|Publica: OrderCreated| Event
Produccion -->|Escucha: OrderCreated| Event
Finanzas -->|Escucha: OrderCreated| Event
Logistica -->|Escucha: OrderCreated| Event
Master -->|Datos| DB
All -->|Sync cada noche| Warehouse
A diferencia de un SaaS multi-tenant, un ERP es monolito en datos pero modular en servicios.
Sección 9.1: La Complejidad de Cambios en Cascada
Cuando cambias el precio de un producto:
Cambio precio --> Órdenes futuras usan nuevo precio
--> Pero ¿qué pasa con órdenes pendientes?
--> ¿Facturas enviadas?
--> ¿Acuerdos con clientes?
Es un caos. La solución es auditoría completa:
// Cuando alguien cambia un precio
async function updateProductPrice(productId, newPrice, userId) {
const oldPrice = await db.products.findById(productId);
// 1. Cambiar el precio
await db.products.update(productId, { price: newPrice });
// 2. Registrar el cambio (auditoría)
await db.audit_log.insert({
type: "PRICE_CHANGE",
entityId: productId,
oldValue: oldPrice,
newValue: newPrice,
userId: userId,
timestamp: new Date(),
reason: req.body.reason,
});
// 3. Notificar a sistemas downstream
await eventBus.publish("product.price_changed", {
productId,
oldPrice: oldPrice,
newPrice: newPrice,
});
// 4. Recalcular órdenes abiertas
const openOrders = await db.orders.where({
status: "PENDING",
items: { some: { productId } },
});
for (const order of openOrders) {
await recalculateOrderTotal(order.id);
}
}
Sección 10: CQRS - Command Query Responsibility Segregation
Imagina un dashboard que necesita mostrar:
- Total de órdenes procesadas
- Revenue en tiempo real
- Cliente top 10
- Tasa de completitud
Las queries son complejas (múltiples joins, agregaciones). Hacer esto en la BD transaccional es lento.
CQRS dice: Tienes dos caminos
- Command Path (escritura): Procesar órdenes, cambiar estado
- Query Path (lectura): Mostrar dashboards, reportes
graph TB
subgraph Write["Write Side (Órdenes)"]
Client["Cliente"]
API["API"]
Events["Event Bus"]
DB["PostgreSQL<br/>(Fuente de verdad)"]
end
subgraph Read["Read Side (Dashboards)"]
Projections["Projections<br/>(Datos pre-procesados)"]
Cache["Redis<br/>(Cache)"]
Analytics["MongoDB<br/>(Optimizado para lectura)"]
end
Client -->|Crear orden| API
API -->|Guarda| DB
DB -->|Publica| Events
Events -->|Procesa| Projections
Projections -->|Actualiza| Analytics
Projections -->|Invalida| Cache
Dashboard["Dashboard"]
Dashboard -->|¿Órdenes totales?| Analytics
La magia: Projections transforman los datos para lectura:
// Cuando se crea una orden
eventBus.on("order.created", async (event) => {
// Actualizar projection de "órdenes por día"
await analyticsDB.update(
{ _id: `day_${event.date}` },
{ $inc: { count: 1, total_amount: event.amount } },
{ upsert: true },
);
// Invalidar cache
await redis.del("dashboard:summary");
});
// Cuando alguien consulta el dashboard
app.get("/dashboard/summary", async (req, res) => {
// Revisar cache
let data = await redis.get("dashboard:summary");
if (data) {
return res.json(JSON.parse(data));
}
// Si no está en cache, consultar MongoDB (lectura, muy rápido)
data = await analyticsDB.collection("order_stats").findOne({});
// Guardar en cache por 1 minuto
await redis.set("dashboard:summary", JSON.stringify(data), "EX", 60);
return res.json(data);
});
Ventajas:
- Dashboards rápidos (lecturas optimizadas)
- Escrituras sin bloqueos (órdenes procesadas sin esperar analytics)
- Escalabilidad (replica DB de lectura 100 veces si quieres)
Desventajas:
- Complejidad (ahora tienes 2 sistemas de datos)
- Consistencia eventual (el dashboard muestra datos de 30 segundos atrás)
Parte IV: Antipatrones - Las Decisiones que Lamentarás
Sección 11: Antipatrones Arquitectónicos
Cada patrón tiene su espejo oscuro. Aquí están los más comunes.
Antipatrón 1: La Arquitectura Grande y Monolítica (Big Ball of Mud)
Empiezas con monolito. Genial, rápido. Pero después de 3 años:
src/
├── users.ts (5,000 líneas)
├── products.ts (8,000 líneas)
├── orders.ts (12,000 líneas)
├── payments.ts (3,000 líneas)
├── notifications.ts (2,000 líneas)
├── analytics.ts (4,000 líneas)
└── utils.ts (15,000 líneas, todo se usa en todo)
Cada archivo usa 20 otros. No hay bordes claros. Es caos.
Síntomas:
- Cambiar una línea en users.ts requiere entender payments.ts
- Tests tardan 30 minutos en correr
- Merge conflicts cada mañana
- “Nadie entiende cómo funciona X”
Solución:
- Restructurar el monolito en módulos claros (Monolito Modular)
- O extraer servicios críticos
Antipatrón 2: Premature Microservices
Tu startup tiene 3 ingenieros. Decides: “Vamos a usar Kubernetes con 20 microservicios.”
Ahora gastan:
- 2 semanas en CI/CD pipelines
- 1 mes en infraestructura
- Debugging toma 10 horas (el problema está en uno de 20 servicios)
Síntomas:
- Equipo pequeño, arquitectura para 200 personas
- “¿Por qué tarda tanto agregar una feature simple?”
- Infraestructura cuesta más que ingenieros
Solución:
- Empieza monolito
- Cuando sientas el dolor (verdadero dolor, no teórico), refactoriza
Antipatrón 3: Shared Database (Acoplamiento de Datos)
Servicio A ← PostgreSQL → Servicio B
Ambos servicios usan la misma tabla. Ahora:
- Cambiar un schema afecta ambos
- Cambios en A requieren que B esté listo
- No puedes escalar A sin escalar B
Solución:
- Cada servicio su BD
- Si necesitan compartir datos, vía APIs
Antipatrón 4: Cache-Driven Architecture
Cuando tu BD es lenta, agregas Redis. Cuando Redis se llena, agregas memcached. Ahora tienes:
Aplicación → Redis → Memcached → DB
Problema: Invalidación de cache es NP-hard (incluso Phil Karlton dijo “solo hay dos cosas difíciles en CS: invalidación de cache y nombrar cosas”).
Si invalidas cache de forma inconsistente:
- Cliente A ve datos old
- Cliente B ve datos nuevos
- Transacciones fallan
Solución:
- Primero optimiza la BD (índices, queries)
- Luego cache si realmente lo necesitas
- Si usas cache, ten una estrategia de invalidación clara
Antipatrón 5: ¿Serverless para TODO?
Lambda/Cloud Functions es increíble para casos específicos (webhooks, processamiento asincronos). Pero no para:
- Aplicaciones con estado
- Queries a BD muy frecuentes (cold starts = 1 segundo)
- Lógica compleja con múltiples pasos
Síntoma:
- “Por qué nuestro dashboard tarda 5 segundos en cargar? Tiene 3 Lambdas.”
- Cold starts destruyendo UX
Solución:
- Usa Serverless estratégicamente (background jobs, webhooks)
- Servidor tradicional para request-response
Antipatrón 6: No Medir Nada
Lanzas tu sistema. “Está rápido” dicen. Después:
- 10,000 usuarios llegó y todo se cae
- ¿Dónde? ¿DB? ¿Red? ¿Código?
- No tienes idea
Solución:
- Observabilidad desde día 1:
- Logs (ELK Stack, Datadog)
- Métricas (Prometheus, CloudWatch)
- Traces (Jaeger, DataDog)
Antipatrón 7: Integración Síncrona en Cadena
Usuario → API → Servicio A → Servicio B → Servicio C
Si C es lento, todo se congela. Si C cae, todo falla.
Solución:
- Usa mensajes asincronos
- Define timeouts
- Circuit breakers para fallos en cascada
Sección 12: Las Preguntas que Debes Hacer
Cuando alguien te propone una arquitectura, estas preguntas revelan si saben lo que hacen:
1. “¿Cuál es el plan de escala a 10x usuarios?”
Si no tienen respuesta, es arquitectura de juguete.
2. “¿Cómo manejamos fallos?”
- ¿Qué pasa si la BD cae?
- ¿Qué pasa si una red se desconecta?
- ¿Recovery RTO y RPO?**
3. “¿Cuál es el costo de esta arquitectura?”
Microservicios escalan mejor pero cuestan 10x. ¿Vale la pena?
4. “¿Quién la mantiene?”
¿Necesitas un DevOps dedicado? ¿El equipo entiende?
5. “¿Cuándo la cambiar?”
Ninguna arquitectura es permanente. ¿Cuáles son los síntomas de cambio?
Parte V: Decisiones Arquitectónicas en el Mundo Real
Sección 13: Estudio de Casos - Cómo lo Hacen las Grandes Empresas
Stripe - El Procesador de Pagos Global
Stripe procesa $1 billones en transacciones anuales. Su arquitectura:
- Backend principal: Monolito en Ruby on Rails (sí, Ruby)
- Porque: ACID es crítico para dinero. Un monolito garantiza consistencia.
- Escala horizontal: Múltiples instancias del monolito detrás de un load balancer
- Servicios independientes: Webhooks, reporting, analytics (microservicios)
- Lección: No necesitas microservicios para ser global. Necesitas una BD bien diseñada y operaciones excelentes.
Netflix - El Pionero de Microservicios
Netflix fue de monolito (2008) a cientos de microservicios (2020).
- Por qué: Escala masiva (500M+ suscriptores en 190 países)
- Tecnología: Spring Boot + Kubernetes + Chaos Engineering
- Lección: Microservicios te permiten innovar rápido y deployar independientemente. Pero requiere madurez operacional.
Amazon - Dos Pizzas
Amazon tiene una regla: un equipo debe poder ser alimentado con dos pizzas. Si es más grande, divide.
Esto llevó a:
- Docenas de servicios (EC2, S3, RDS, DynamoDB, etc.)
- Cada uno operado por un pequeño equipo
- Cada uno con su propia BD, API, ciclo de release
- Lección**: Equipos pequeños + servicios independientes = innovación rápida
GitHub - Estable en Monolito
GitHub es principalmente un monolito de Ruby on Rails.
- Escala global pero único datastore principal
- Servicios satelitales para search (Elasticsearch), actions (CI/CD)
- Lección**: Si tu monolito es bien escrito y bien operado, no necesitas microservicios.
Sección 14: Decision Framework - Eligiendo Arquitectura
Aquí hay un framework que puedes usar para cualquier proyecto:
┌─────────────────────────────────────────┐
│ 1. ¿Cuál es el scope del proyecto? │
│ (monolito vs múltiples servicios) │
└────────────┬────────────────────────────┘
│
┌────▼────────────────────────────┐
│ 2. ¿Cuál es la escala esperada? │
│ (<1M, 1M-100M, >100M users) │
└────┬─────────────────────────────┘
│
┌────▼──────────────────────────────┐
│ 3. ¿Cuántos equipos? │
│ (<5, 5-20, >20 equipos) │
└────┬───────────────────────────────┘
│
┌────▼────────────────────────────┐
│ 4. ¿Cuáles son las criticidades?│
│ (si algo cae, qué se rompe?) │
└────┬─────────────────────────────┘
│
┌────▼──────────────────────────┐
│ 5. ¿Cuál es el presupuesto? │
│ (infraestructura + personas)│
└──────────────────────────────┘
Ejemplo: Sistema de Logística
1. Scope: Sistema complejo
- Múltiples clientes (drivers, managers, customers)
- Datos en tiempo real
→ Necesita modularidad
2. Escala: 1-100M users
- Comienza con 10,000 conductores
- Crece a 1 millón en 3 años
→ Horizontal scalability es crítica
3. Equipos: 5-10 inicialmente
- Backend team
- Frontend team
- DevOps team
→ Necesita cierta independencia
4. Criticidad:
- Si tracking se cae: clientes no ven dónde está paquete (malo pero tolerable)
- Si órdenes se cae: nadie puede mandar paquetes (crítico)
- Si routing se cae: rutas desoptimizadas (annoying pero funciona)
→ Orders es el corazón
5. Presupuesto: $2-5M anuales
- Infraestructura: $800k
- Equipo: $3M
→ Pueden hacer DevOps bien
DECISIÓN:
- Core monolito modular para Orders + Inventory
- Microservicios independientes para: Tracking, Routing, Notifications
- Multi-BFF para drivers app, manager dashboard, customer tracking
- Event-driven para comunicación
Conclusión
Hemos viajado desde “¿qué es arquitectura?” hasta decisiones que toman empresas de miles de millones de dólares.
La realidad es esta: No hay arquitectura perfecta. Solo hay trade-offs.
Monolito es simple pero no escala. Microservicios escalan pero son complejos. Serverless es barato pero lento. Caches son rápidos pero fallan.
Lo que diferencia a un arquitecto junior de uno senior es esto:
- Conocer los trade-offs (en tu cabeza, no en documentos)
- Saber cuándo cambiar (antes de que el sistema explote, pero no antes de tiempo)
- Comunicar decisiones (a ingenieros, managers, ejecutivos)
- Hacer pragmático (perfecto es enemigo de hecho)
La próxima vez que alguien te diga “debemos usar microservicios,” pregunta:
- ¿Por qué?
- ¿Cuándo lo sabremos?
- ¿Quién lo mantiene?
- ¿Cuánto cuesta?
Las respuestas revelan si es arquitectura bien pensada o moda.
Recursos Adicionales
- “Designing Data-Intensive Applications” - Martin Kleppmann (lectura obligatoria)
- “Building Microservices” - Sam Newman
- “Release It!” - Michael Nygard (estabilidad y operaciones)
- C4 Model: c4model.com
- Kafka Streams Architecture: kafka.apache.org
- Event Sourcing: martinfowler.com/eaaDev/EventSourcing.html
Última nota: Cuando diseñes un sistema nuevo, empieza simple. Diseña como si fuera a durar 5 años. Porque probablemente durará 20.
Tags
Artículos relacionados
Arquitectura de software: Más allá del código
Una guía completa sobre arquitectura de software explicada en lenguaje humano: patrones, organización, estructura y cómo construir sistemas que escalen con tu negocio.
Clean Architecture: Construyendo software que perdura
Una guía completa sobre Clean Architecture explicada en lenguaje humano: qué es cada capa, cómo se integran, cuándo usarlas y por qué importa para tu negocio.
Construir un Backend escalable y eficiente desde 0
Una guía paso a paso para implementar una arquitectura hexagonal en Go 1.25