Después de dos décadas diseñando sistemas distribuidos, hay una lección que se repite: los problemas de escalabilidad rara vez se resuelven con más cómputo. Se resuelven con mejores patrones. En AWS, tres patrones concentran la mayor parte del valor arquitectónico que he visto funcionar en producción: CQRS, Event Sourcing y descomposición en microservicios bien delimitados.
CQRS: separar lectura de escritura
Command Query Responsibility Segregation separa el modelo que escribe datos del modelo que los lee. En sistemas con cargas de lectura muy superiores a las de escritura —el caso típico de dashboards, catálogos o reportes— esto permite escalar cada lado de forma independiente.
Una implementación común en AWS combina DynamoDB como store transaccional para comandos con OpenSearch o una vista materializada en RDS para las consultas complejas:
// Comando: escribe en DynamoDB como fuente de verdad
export async function createOrder(command: CreateOrderCommand) {
await dynamoClient.send(
new PutItemCommand({
TableName: 'Orders',
Item: marshall({ ...command, status: 'PENDING', createdAt: Date.now() }),
})
)
}
// Proyección: un stream de DynamoDB alimenta la vista de lectura
export async function projectOrderToReadModel(record: DynamoDBRecord) {
const order = unmarshall(record.dynamodb.NewImage)
await openSearchClient.index({
index: 'orders-read-model',
id: order.id,
body: order,
})
}
El costo de este patrón es la consistencia eventual: la vista de lectura se actualiza con milisegundos de rezago. En la mayoría de los dominios de negocio ese costo es aceptable; en dominios financieros con requisitos de consistencia fuerte, no lo es, y ahí prefiero un modelo transaccional único.
Event Sourcing: el historial como fuente de verdad
En lugar de persistir el estado actual de una entidad, Event Sourcing persiste la secuencia de eventos que la llevaron a ese estado. El estado se deriva reproduciendo los eventos. En AWS esto se implementa naturalmente con Kinesis Data Streams o EventBridge como columna vertebral, y DynamoDB o S3 como almacén de eventos inmutable.
El beneficio real no es técnico, es de negocio: cuando el historial completo existe, preguntas como "¿por qué este pedido terminó en este estado?" dejan de requerir arqueología en logs. También habilita reconstruir proyecciones nuevas sin migrar datos, simplemente reproduciendo el stream contra un nuevo consumidor.
La contrapartida es la complejidad operacional: versionar eventos, manejar snapshots para evitar reproducir historiales completos, y diseñar consumidores idempotentes. Lo reservo para dominios donde la auditabilidad es un requisito de negocio explícito, no un "nice to have".
Microservicios: el límite importa más que el tamaño
El error más común que veo en migraciones a microservicios en AWS no es hacer los servicios "demasiado grandes"; es trazar los límites por capa técnica en vez de por dominio de negocio. Un servicio de "usuarios" que también resuelve autenticación, preferencias y facturación no es un microservicio, es un monolito con más saltos de red.
En AWS, el patrón que mejor ha funcionado en mis proyectos combina:
- API Gateway como punto de entrada único, con autorización centralizada.
- ECS Fargate o Lambda por servicio, según el perfil de carga (Fargate para cargas sostenidas, Lambda para eventos esporádicos o picos).
- SQS/SNS o EventBridge para comunicación asíncrona entre servicios, evitando llamadas síncronas encadenadas que propagan fallos.
- Un API Gateway con contratos versionados para que cada servicio pueda evolucionar sin romper a sus consumidores.
# Ejemplo simplificado de una definición SAM para un servicio de dominio
Resources:
OrdersFunction:
Type: AWS::Serverless::Function
Properties:
Handler: dist/handler.main
Runtime: nodejs20.x
Events:
CreateOrder:
Type: Api
Properties:
Path: /orders
Method: post
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref OrdersTable
La regla que aplico siempre
Ningún patrón es gratis. CQRS agrega consistencia eventual, Event Sourcing agrega complejidad de replay, los microservicios agregan latencia de red y superficie operacional. La pregunta correcta nunca es "¿es este patrón moderno?", sino "¿el problema que tengo justifica el costo que este patrón introduce?". La arquitectura madura no es la que usa más patrones; es la que usa los mínimos necesarios para el problema real.