¿Por qué CQRS y Event Sourcing no son "para unos pocos"?
\n\nCuando escuchamos las frases "CQRS" o "Event Sourcing", muchos desarrolladores de JavaScript imaginan algo del mundo de la arquitectura empresarial en Java o C#. Esquemas complejos, eventos interminables, almacenamientos extraños... Pero, ¿y si te digo que estos patrones se pueden (¡y deben!) usar en aplicaciones Node.js comunes?
\n\nEn este artículo, analizaremos cómo CQRS y Event Sourcing ayudan a resolver problemas reales: conflictos de datos, registro complejo, escalabilidad y depuración. Y todo esto — en JavaScript puro, sin magia ni frameworks complejos.
\n\n¿Qué es CQRS (Command Query Responsibility Segregation)?
\n\nImagina que tu aplicación es un restaurante. Los camareros (consultas) y los cocineros (comandos) trabajan en el mismo espacio. Tarde o temprano comenzarán a estorbarse mutuamente. CQRS propone separar estas zonas: los camareros trabajan en el salón (lectura) y los cocineros en la cocina (escritura).
\n\nCQRS es un principio de separación de operaciones en dos canales:
\n- \n
- Comandos (Commands) — modifican el estado del sistema (Create, Update, Delete) \n
- Consultas (Queries) — solo leen datos (Read) \n
En una API REST clásica, normalmente usamos el mismo objeto para leer y escribir. Por ejemplo, UserController contiene los métodos GET /users/:id y POST /users. CQRS propone separarlos en diferentes capas.
Ejemplo simple de CQRS en JavaScript
\n\n// Comando (cambio de estado)\nclass CreateUserCommand {\n constructor(name, email) {\n this.name = name;\n this.email = email;\n }\n}\n\nclass UserCommandHandler {\n async handle(command) {\n // Validación, lógica de negocio, guardado\n return await db.users.insert({\n name: command.name,\n email: command.email,\n created_at: new Date()\n });\n }\n}\n\n// Consulta (solo lectura)\nclass GetUserQuery {\n constructor(userId) {\n this.userId = userId;\n }\n}\n\nclass UserQueryHandler {\n async handle(query) {\n // Solo lectura, ¡sin cambios!\n return await db.users.findOne({ _id: query.userId });\n }\n}\n\n¿Parece que esto complica el código? Sí, a primera vista. Pero cuando tu aplicación crezca a 50+ endpoints, la separación de comandos y consultas te salvará de:
\n- \n
- Modificaciones accidentales de datos en solicitudes GET \n
- Problemas con el almacenamiento en caché (las consultas se cachean fácilmente, los comandos no) \n
- Conflictos cuando varios desarrolladores trabajan en paralelo \n
Event Sourcing: recordamos cada acción
\n\nAhora imagina que, en lugar de almacenar el estado actual de un usuario, almacenas todos los eventos que le han ocurrido. No "Iván, 25 años, Moscú", sino una secuencia:
\n- \n
- "Usuario creado: Iván" \n
- "Usuario cambió edad a 25" \n
- "Usuario se mudó a Moscú" \n
Event Sourcing es un patrón donde cada cambio de estado se guarda como un evento inmutable. El estado actual se calcula "reproduciendo" todos los eventos.
\n\nImplementación de Event Store en JavaScript
\n\nclass EventStore {\n constructor() {\n this.events = [];\n }\n\n append(aggregateId, event) {\n this.events.push({\n aggregateId,\n event,\n timestamp: new Date()\n });\n }\n\n getEvents(aggregateId) {\n return this.events.filter(e => e.aggregateId === aggregateId);\n }\n}\n\n// Reconstrucción del estado a partir de eventos\nfunction rebuildState(events) {\n return events.reduce((state, event) => {\n switch(event.event.type) {\n case 'USER_CREATED':\n return { ...state, name: event.event.name, age: 0 };\n case