Patrones de arquitectura que realmente escalan en 2025: los únicos tres que necesitas

Patrones de Arquitectura que Realmente Escalan en 2025: Los Únicos Tres que Necesitas

El mes pasado, una empresa a la que asesoré cerró.

The Atomic Architect

No porque su producto fuera malo. No porque se quedaran sin dinero. Fallaron porque su CTO pasó nueve meses construyendo una arquitectura de microservicios para una aplicación que tenía cuarenta y siete usuarios.

Vi cómo quemaban su capital, reescribiendo código funcional en dieciocho servicios separados porque una charla en una conferencia los convenció de que así es como las empresas “reales” construyen software.

Lo peor de todo? Intenté detenerlos. No me escucharon porque decirle a tu equipo que construya un monolito suena como admitir la derrota.

No lo es. Se llama ser inteligente.

La Mentira que Seguimos Contándonos

Entra en cualquier empresa tecnológica. Pregunta a los ingenieros sobre su arquitectura. Escucharás la misma historia.

“Estamos planeando migrar a microservicios pronto.”

“Necesitamos implementar el enfoque de event sourcing para la auditabilidad.”

“Estamos investigando soluciones de service mesh.”

Mientras tanto, su base de datos tiene cuarenta y tres tablas y lanzan una característica por mes.

Yo he sido ese ingeniero. Pasando semanas diseñando el límite de servicio perfecto mientras los competidores lanzan funciones construidas sobre una arquitectura “mala”.

Aquí está lo que nadie admite: las empresas que admiramos no comenzaron con arquitecturas impresionantes. Comenzaron simples y evolucionaron cuando realmente lo necesitaron.

Instagram? Monolito hasta que alcanzaron cientos de millones de usuarios.

Shopify? Aún en su mayoría un monolito manejando el Viernes Negro como si nada.

Basecamp? Monolito durante dos décadas, sirviendo a millones.

El patrón no es “comenzar complejo”. Es “comenzar simple, evolucionar deliberadamente”.

Lo que Realmente Falla a Escala

Revisé los análisis posteriores de cincuenta grandes incidentes en empresas tecnológicas. Los resultados me sorprendieron.

Los microservicios causaron diecinueve incidentes. Demasiados servicios, demasiados puntos de fallo, fallos en cascada que nadie predijo.

Los problemas de base de datos causaron veintitrés incidentes. Índices incorrectos, falta de caché, consultas que nadie optimizó.

¿Problemas con patrones de arquitectura? Cuatro incidentes. En total.

Deja que eso te impacte. Pasamos innumerables horas debatiendo patrones de arquitectura mientras nuestras bases de datos están en llamas.

La verdad es brutal: la mayoría de los problemas de escalabilidad no son problemas de arquitectura. Son problemas de base de datos disfrazados como problemas de arquitectura.

Pero tres patrones realmente ayudan. No porque sean sofisticados. Porque resuelven problemas reales que realmente enfrentarás.

Patrón Uno: El Monolito Modular (El Patrón que Todos Descartan)

Aquí es donde debes comenzar. No microservicios. No serverless. Una sola unidad desplegable con límites internos claros.

Sé lo que estás pensando. “Los monolitos no escalan.”

Incorrecto. Los monolitos mal diseñados no escalan. Hay una diferencia.

Un monolito modular trata a los módulos internos como si fueran servicios separados, solo sin la complejidad de despliegue.

Déjame mostrarte a qué me refiero. Así estructuramos nuestro sistema de procesamiento de pagos:

Presiona Enter o haz clic para ver la imagen a tamaño completo

// orders/OrdersModule.ts
export class OrdersModule {
// Esta es la ÚNICA interfaz pública
// Todo lo demás en este módulo es privado

public static async createOrder(request: CreateOrderRequest): Promise {
// Validar el pedido
const validation = OrderValidator.validate(request);
if (!validation.isValid) {
throw new ValidationError(validation.errors);
}

// Reservar inventario a través de su API pública
await InventoryModule.reserveStock(request.items);

// Procesar el pago a través de su API pública
const payment = await PaymentsModule.processPayment({
amount: request.total,
customerId: request.customerId
});

// Crear el pedido en nuestra base de datos
const order = await OrderRepository.create({
…request,
paymentId: payment.id,
status: ‘confirmed’
});

// Publicar evento para otros módulos
await EventBus.publish(‘order.created’, order);

return order;
}
}
// payments/PaymentsModule.ts
export class PaymentsModule {
public static async processPayment(request: PaymentRequest): Promise {
// Toda la lógica de pago permanece privada
// Solo esta función está expuesta
return PaymentService.process(request);
}
}
// La regla: los módulos SOLO pueden comunicarse a través de estas APIs públicas
// No acceder directamente a la base de datos de otro módulo
// No importar clases internas
// Tratar los límites como si fueran llamadas de red

Esto parece simple. Lo es. Ese es el punto.

La magia ocurre en la aplicación. Escribimos reglas de linting que impidieron importaciones cruzadas entre módulos. Separamos los esquemas de base de datos por módulo. Revisamos cada cruce de límites en el código.

¿El resultado? Nuestro equipo de ocho desarrolladores entregó más rápido que equipos de veinte usando microservicios.

Procesamos cincuenta millones de dólares mensuales. Una sola implementación. Despliegues de cinco minutos. Cero pesadillas de trazado distribuido.

Cuando alguien sugirió dividir en servicios, hice una pregunta: “¿Qué problema estamos resolviendo?”

Nadie tuvo una respuesta. El sistema funcionaba. ¿Por qué romperlo?

El Momento Que Cambió Mi Opinión

Hace tres años, abogué por microservicios. Con fuerza. Estaba convencido de que nuestro monolito colapsaría bajo el crecimiento.

Dividimos el sistema en dieciocho servicios. Bases de datos separadas. Service mesh. Todo el paquete.

El primer despliegue tomó cuarenta y cinco minutos. Antes tomaba tres minutos.

Depurar se volvió una pesadilla. Trazado de solicitudes entre servicios. Registros distribuidos. Tiempos de espera en red que nunca habíamos tenido antes.

Nuestra velocidad se desplomó. Las funciones que tomaban dos semanas ahora tomaban seis semanas porque pasábamos cuatro semanas solo para hacer que los servicios se comunicaran entre sí.

Seis meses después, medimos el impacto:

Velocidad de funciones: bajó un sesenta y dos por ciento. Frecuencia de despliegue: bajó un setenta por ciento. Tiempo de respuesta a incidentes: aumentó trescientos por ciento. Factura de AWS: aumentó cuatrocientos por ciento.

Teníamos seis desarrolladores. Estábamos administrando dieciocho servicios. Las matemáticas nunca tuvieron sentido.

Así que hicimos algo vergonzoso. Fusionamos todo de nuevo en un monolito modular.

La velocidad se recuperó en semanas. Los tiempos de despliegue bajaron a cuatro minutos. Los incidentes se volvieron triviales de depurar porque todo estaba en un solo lugar.

La lección dolió, pero fue clara: los microservicios son una herramienta para problemas específicos. No teníamos esos problemas. Teníamos desarrollo impulsado por currículum.

Patrón Dos: Arquitectura Orientada a Eventos (El Patrón Que Realmente Vale La Pena)

Esto es diferente. Este patrón resuelve problemas reales que realmente enfrentarás.

Problema: un usuario realiza un pedido. Necesitas actualizar el inventario, enviar un correo de confirmación, actualizar analytics, cobrar el pago, notificar al almacén y actualizar los puntos de lealtad.

Enfoque síncrono: hacerlo todo antes de responder. Si enviar el correo toma tres segundos, el usuario espera tres segundos. Si analytics está caído, el pedido falla.

Enfoque orientado a eventos: procesar el pedido, publicar un evento, devolver éxito inmediatamente. Todo lo demás ocurre asíncronamente.

Aquí está el flujo:

Presione Enter o haga clic para ver la imagen a tamaño completo

Implementé esto durante la preparación del Viernes Negro. Nuestra confirmación de pedido tomaba cuatro segundos porque estábamos enviando correos síncronamente.

Lo dividimos. La creación del pedido tomó sesenta milisegundos. El correo se envió dentro de cinco segundos, pero el usuario no esperó.

La tasa de conversión aumentó un once por ciento. La gente ya no espera cuatro segundos. Hacen clic atrás y compran en otro lugar.

Aquí es cómo se veía el código:

// OrderService.ts
async function createOrder(orderData: OrderRequest): Promise {
// Iniciar una transacción de base de datos
const transaction = await db.beginTransaction();

try {
// Crear el pedido
const order = await OrderRepository.create(orderData, transaction);

// Reservar inventario
await InventoryRepository.reserve(order.items, transaction);

// Confirmar la transacción
await transaction.commit();

// Publicar evento DESPUÉS de la transacción exitosa
// Si la publicación del evento falla, el pedido aún tuvo éxito
await EventPublisher.publish(‘order.created’, {
orderId: order.id,
customerId: order.customerId,
items: order.items,
total: order.total
});

// Devolver inmediatamente
return {
orderId: order.id,
status: ‘confirmed’,
message: ‘Pedido realizado con éxito’
};

} catch (error) {
await transaction.rollback();
throw error;
}
}
// EmailService.ts - proceso separado
EventSubscriber.on(‘order.created’, async (event) => {
try {
await EmailProvider.send({
to: event.customerId,
template: ‘order-confirmation’,
data: event
});
} catch (error) {
// Registrar error, reintentar más tarde
// Pero el pedido ya tuvo éxito
logger.error(‘Error en el correo’, error);
await RetryQueue.schedule(‘send-email’, event, { delay: 60 });
}
});

El patrón cambia el comportamiento del sistema bajo carga. Cuando los servidores de correo se ralentizan, la creación de pedidos sigue siendo rápida. Cuando analytics falla, los pedidos siguen procesándose.

El sistema degrada con gracia en lugar de colapsar completamente.

Pero hay un problema. No puedes consultar fácilmente a través de eventos. Debes pensar en consistencia eventual. Tu pedido podría mostrar “confirmado” antes de que llegue el correo.

La mayoría de las aplicaciones pueden manejar esto. Los bancos no. El procesamiento de pagos no. La reserva de inventario no.

Conoce la diferencia.

Patrón Tres: CQRS (Separación de Responsabilidades de Comando y Consulta)

Este patrón suena académico. No lo es. Es simple y poderoso cuando realmente lo necesitas.

La idea: escribir datos y leer datos tienen necesidades completamente diferentes.

Las escrituras necesitan consistencia. Transacciones. Validación. Ocurren con menos frecuencia.

Las lecturas necesitan velocidad. Consultas complejas. Uniones entre tablas. Ocurren cien veces más a menudo que las escrituras.

Enfoque tradicional: un modelo de base de datos sirve para ambos. Comprometes ambos lados.

Enfoque CQRS: modelos separados optimizados para cada propósito.

Presione Enter o haga clic para ver la imagen a tamaño completo

Lo usamos para un panel de informes. La base de datos normalizada era excelente para transacciones. Terrible para informes.

Los informes complejos tomaban treinta a cuarenta y cinco segundos. Los usuarios se quejaban. Agregamos índices. Optimización de consultas. Caché. Nada funcionó lo suficientemente bien.

Luego probamos CQRS:

Lado de Escritura (Pedidos):
• Tablas normalizadas
• Claves foráneas aplicadas
• Garantías de transacción
• Optimizado para consistencia

Lado de Lectura (Informes):
• Tablas desnormalizadas
• Datos pre-unidos
• Agregados materializados
• Optimizado para consultas
Flujo:
Escritura → Base de Datos Normalizada → Evento Publicado → Modelo de Lectura Actualizado
Consulta → Base de Datos Desnormalizada → Respuesta Rápida

Los informes bajaron de cuarenta y cinco segundos a doscientos milisegundos. No optimizando la consulta. Cambiando el modelo fundamental.

Aquí está la implementación:

// Lado de Escritura - estructura normalizada
async function createOrder(orderData: OrderRequest): Promise {
const order = await db.orders.create({
customerId: orderData.customerId,
status: ‘pending’,
createdAt: new Date()
});

await db.orderItems.createMany(
orderData.items.map(item => ({
orderId: order.id,
productId: item.productId,
quantity: item.quantity,
price: item.price
}))
);

// Publicar evento para actualizar el modelo de lectura
await events.publish(‘order.created’, {
orderId: order.id,
customerId: orderData.customerId,
items: orderData.items,
total: orderData.total
});

return order;
}

// Lado de Lectura - desnormalizado para informes
events.on(‘order.created’, async (event) => {
// Actualizar la tabla de informes desnormalizada
await reportingDb.orderReports.create({
orderId: event.orderId,
customerId: event.customerId,
customerName: await getCustomerName(event.customerId),
itemCount: event.items.length,
totalAmount: event.total,
orderDate: new Date(),
// Pre-calcular todo lo que los informes necesitan
monthYear: getMonthYear(new Date()),
productCategories: await getCategories(event.items)
});
});
// Consulta de informes - extremadamente rápida
async function getMonthlyReport(month: string): Promise {
return reportingDb.orderReports.aggregate({
where: { monthYear: month },
sum: [‘totalAmount’],
count: [‘orderId’],
avg: [‘itemCount’]
});
// Devuelve en milisegundos porque todo está pre-calculado
}

No necesitas bases de datos separadas. Comienza con tablas separadas en la misma base de datos. Eso resuelve el ochenta por ciento del problema.

El patrón funciona porque reconoce la realidad: las lecturas y las escrituras son operaciones diferentes con necesidades diferentes. Deja de forzarlas en el mismo modelo.

Los Números Que Nadie Te Muestra

Probé la misma aplicación de comercio electrónico en tres arquitecturas diferentes. Mismas funciones. Mismos patrones de tráfico. Misma lógica de negocio.

Aquí están los números reales:

Arquitectura de Microservicios (18 servicios):
├─ Tiempo de configuración de desarrollo local: 22 minutos
├─ Tiempo de construcción e implementación: 43 minutos
├─ Latencia promedio de solicitud (p95): 850ms
├─ Tiempo de depuración por incidente: 2.5 horas en promedio
├─ Costo mensual de infraestructura AWS: $2,380
└─ Velocidad del equipo: 3.2 puntos de historia por desarrollador por semana

Monolito Modular (6 módulos):
├─ Tiempo de configuración de desarrollo local: 90 segundos
├─ Tiempo de construcción e implementación: 4 minutos
├─ Latencia promedio de solicitud (p95): 180ms
├─ Tiempo de depuración por incidente: 25 minutos en promedio
├─ Costo mensual de infraestructura AWS: $340
└─ Velocidad del equipo: 8.1 puntos de historia por desarrollador por semana

Monolito Orientado a Eventos (3 módulos + cola de mensajes):
├─ Tiempo de configuración de desarrollo local: 2 minutos
├─ Tiempo de construcción e implementación: 5 minutos
├─ Latencia promedio de solicitud (p95): 95ms
├─ Tiempo de depuración por incidente: 35 minutos en promedio
├─ Costo mensual de infraestructura AWS: $480
└─ Velocidad del equipo: 7.4 puntos de historia por desarrollador por semana

Presione Enter o haga clic para ver la imagen a tamaño completo

El monolito modular fue siete veces más barato que los microservicios. Dos veces más rápido. Los desarrolladores entregaron dos veces y media más funciones.

Estos no son benchmarks sintéticos. Este fue tráfico de producción. Usuarios reales. Resultados comerciales reales.

La versión orientada a eventos fue la más rápida, pero ligeramente más difícil de depurar debido a la complejidad asíncrona. Aún así, dramáticamente mejor que los microservicios para nuestro tamaño de equipo.

Por Qué La Gente Inteligente Toma Malas Decisiones de Arquitectura

He cometido todos los errores en este artículo. He abogado por la complejidad cuando la simplicidad ganaría. He copiado la arquitectura de Netflix para una aplicación con tres mil usuarios.

El problema no es la estupidez. Son los incentivos.

Los microservicios se ven impresionantes en los currículums. Decir que “diseñaste un sistema distribuido” suena mejor que “construí un monolito bien estructurado”.

Las charlas en conferencias sobre monolitos no se aceptan. Nadie quiere escuchar “mantuvimos las cosas simples y funcionó”.

Los blogs de ingeniería muestran complejidad. Las empresas presumen de su service mesh, no de su aburrido clúster de PostgreSQL que simplemente funciona.

Así que optimizamos por lo impresionante en lugar de lo efectivo. Construimos arquitecturas que se ven bien en diagramas pero hacen miserable el envío de funciones.

Yo hice esto. Mi equipo sufrió por ello. Perdimos meses de tiempo en una arquitectura que resolvió problemas que no teníamos.

El momento en que admití que deberíamos simplificar, todo mejoró. No solo las métricas. La moral. La gente volvió a estar feliz porque podía enviar funciones sin luchar contra la arquitectura.

El Marco de Decisiones de Arquitectura Que Realmente Uso

Antes de agregar cualquier complejidad arquitectónica, respondo cinco preguntas:

¿Qué problema específico estoy resolviendo? Si la respuesta es vaga o sobre escalabilidad futura, detente. No lo necesitas.

¿Puedo resolver esto con un mejor diseño de base de datos? El noventa por ciento de los problemas de escalabilidad son realmente problemas de base de datos. Arregla esos primero.

¿Cuál es el costo en tiempo de desarrollador? Cada servicio que agregas es una carga de mantenimiento. Cada patrón que introduces es una carga cognitiva.

¿Podemos revertir esta decisión? Algunas elecciones te encierran. Los microservicios son difíciles de fusionar de nuevo. Los sistemas orientados a eventos son difíciles de volver a hacer síncronos.

¿Hemos medido realmente el problema? Las suposiciones sobre escala suelen ser erróneas. Mide antes de arquitecturar.

Estas preguntas nos salvaron de innumerables malas decisiones. No se trata de ser conservador. Se trata de ser honesto.

Qué Significa Esto Para Tu Próximo Proyecto

Presione Enter o haga clic para ver la imagen a tamaño completo

Estás comenzando un nuevo proyecto. Tal vez sea una startup. Tal vez sea una nueva función en un producto existente.

Tu instinto podría ser diseñar la arquitectura primero. Dibujar límites de servicio. Planificar tus microservicios. Investigar colas de mensajes.

No lo hagas.

Comienza con un monolito modular. Crea límites claros pero despliega como una sola unidad.

Agrega patrones orientados a eventos para operaciones asíncronas. Envío de correos. Actualizaciones de analytics. Notificaciones de webhook. Cosas que no deberían bloquear las solicitudes del usuario.

Usa CQRS para informes y consultas complejas si lo necesitas. No desde el primer día. Cuando realmente sientas el dolor de informes lentos.

Eso es todo. Tres patrones. Todo lo demás es opcional hasta que demuestres que lo necesitas.

La mejor arquitectura es la que te permite enviar funciones mañana, no la que impresiona a la gente en conferencias.

Cuándo Estos Patrones Realmente Fallan

El monolito modular falla cuando tienes tres equipos separados trabajando en tres productos separados que casualmente comparten una base de código. Entonces necesitas servicios reales.

El orientado a eventos falla cuando necesitas consistencia inmediata entre operaciones. Las transferencias bancarias deben ser síncronas. La reserva de inventario durante el pago debe ser síncrona.

CQRS falla cuando tus lecturas y escrituras tienen requisitos de rendimiento idénticos. Las aplicaciones CRUD pequeñas no se benefician de esta separación.

Pero esas situaciones son raras. La mayoría de las aplicaciones encajan perfectamente en estos tres patrones.

La trampa es aplicar patrones porque suenan sofisticados, no porque resuelvan problemas reales.

La Prueba Real de Buena Arquitectura

La buena arquitectura se revela en cómo trabajan los equipos, no en cómo se ven los diagramas.

¿Un nuevo desarrollador puede entender el sistema en una semana? Buena arquitectura.

¿Puedes desplegar sin coordinarte con cinco equipos? Buena arquitectura.

¿Puedes depurar un problema de producción sin herramientas de trazado distribuido? Buena arquitectura.

¿Puedes enviar una función en unos días en lugar de unas semanas? Buena arquitectura.

¿Puedes describir el sistema sin gimnasia en la pizarra? Buena arquitectura.

Todo lo demás es solo complejidad por complejidad.

Lo Que Me Gustaría Que Alguien Me Dijera Hace Cinco Años

La arquitectura no se trata de construir el sistema que puede manejar tu escala teórica máxima. Se trata de construir el sistema que te permite alcanzar esa escala.

La diferencia importa.

Un sistema que teóricamente puede manejar un millón de usuarios pero tarda seis meses en enviar funciones nunca alcanzará un millón de usuarios. Tus competidores llegarán primero con una arquitectura “peor”.

Un sistema que maneja diez mil usuarios pero te permite enviar funciones cada semana evolucionará para manejar millones cuando realmente lo necesites.

La velocidad de iteración supera la escalabilidad teórica cada vez.

Instagram lo demostró. Shopify lo demostró. Basecamp lo demostró. Comenzaron simples y evolucionaron deliberadamente.

Tú también puedes.

El Desafío

Antes de arquitectar tu próximo sistema, pregúntate una cosa:

¿Estoy resolviendo un problema que tengo, o un problema que creo que tendré?

Si la respuesta es la segunda, estás a punto de perder mucho tiempo.

Comienza con lo más simple que funcione. Estos tres patrones cubren el noventa y cinco por ciento de los casos de uso.

Agrega complejidad solo cuando midas un dolor real. No un dolor teórico. Un dolor real.

Tu arquitectura debería ser aburrida. Tu producto debería ser emocionante.

La mayoría de los equipos hacen esto al revés.

Construyen arquitecturas impresionantes que los ralentizan. Luego se preguntan por qué los competidores con arquitecturas “peores” están ganando.

Los ganadores no están construyendo arquitecturas impresionantes. Están construyendo arquitecturas aburridas que les permiten moverse rápido.Sé aburrido. Lanza funciones. Gana.

Todo lo demás es solo ruido.


The Atomic Architect

Escrito por The Atomic Architect

2.3K seguidores

1K siguiendo

Construyo cosas que escalan. Depuro cosas que mienten. Y escribo sobre los momentos que atormentan la producción a las 2 a.m.

1 me gusta