CQRS y Event Sourcing en Rust: Arquitectura para un rendimiento sin concesiones

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué CQRS y Event Sourcing no son "sobreingeniería", sino un salvavidas para sistemas complejos

\n\n

¿Alguna vez te has encontrado con una situación en la que tu sistema, escrito en Rust, maneja perfectamente las solicitudes de lectura, pero "se cae de rodillas" al escribir? ¿O viceversa: sacrificas el rendimiento de lectura para poder guardar el historial completo de cambios? ¿Te suena familiar? Entonces bienvenido al mundo de CQRS y Event Sourcing.

\n\n

Muchos desarrolladores consideran estos patrones como "artillería pesada", aplicable solo en proyectos empresariales con C# o Java. Pero con el crecimiento de la popularidad de Rust en sistemas de alta carga (desde servidores de juegos hasta plataformas financieras), CQRS y Event Sourcing se vuelven no solo útiles, sino necesarios. Rust, con su costo cero de abstracciones y su tipado estricto, es el lenguaje ideal para implementar estos patrones.

\n\n

En este artículo no solo hablaremos de teoría. Escribiremos código funcional en Rust que podrás adaptar a tus proyectos. Aprenderás cómo separar la responsabilidad entre comandos y consultas, cómo almacenar eventos en lugar del estado actual y cómo todo esto acelera tu sistema varias veces.

\n\n

¿Qué son CQRS y Event Sourcing? En términos simples para un desarrollador de Rust

\n\n

Imagina que administras una cuenta bancaria. En el modelo CRUD tradicional, tienes una tabla "Cuentas" donde se almacena el saldo actual. Si necesitas ver el historial, creas otra tabla "Transacciones". Pero, ¿y si necesitas no solo saber el saldo, sino entender por qué cambió? ¿O restaurar el estado en cualquier fecha?

\n\n

CQRS: Divide y vencerás

\n\n

CQRS (Command Query Responsibility Segregation) es un principio que divide las operaciones en dos grupos:

\n
    \n
  • Comandos (Commands) — modifican el estado. No devuelven datos, solo confirmación o error.
  • \n
  • Consultas (Queries) — solo leen datos. No modifican el estado.
  • \n
\n\n

En Rust, esta división se ve natural: los tipos de comandos y consultas son estructuras diferentes, y sus manejadores son funciones o módulos distintos. Esto no es solo una "buena práctica", es una forma de evitar mutaciones accidentales durante la lectura.

\n\n
// Comando — modifica el estado\npub struct CreateOrder {\n    pub user_id: Uuid,\n    pub items: Vec<OrderItem>,\n}\n\n// Consulta — solo lee\npub struct GetOrderHistory {\n    pub user_id: Uuid,\n    pub limit: usize,\n}
\n\n

Event Sourcing: Almacena el historial, no una instantánea

\n\n

Event Sourcing es un enfoque en el que no guardamos el estado actual de un objeto, sino la secuencia de eventos que llevaron a ese estado. ¿Quieres saber el saldo de la cuenta al 1 de enero de 2023? Simplemente "reproduce" todos los eventos desde el inicio hasta esa fecha.

\n\n

Las ventajas son evidentes:

\n
    \n
  • Auditoría — cada cambio queda registrado.
  • \n
  • Instantáneas temporales — se puede restaurar cualquier estado.
  • \n
  • Resistencia a errores — si hay un error en el código, no pierdes los datos, solo los "reproduces" incorrectamente.
  • \n
\n\n

Ejemplo práctico: Sistema de gestión de pedidos en Rust

\n\n

Construyamos un mini-sistema para una tienda en línea. Procesará comandos (creación de pedido, agregar producto) y consultas (obtener carrito, historial de pedidos). Toda la lógica se basará en eventos.

\n\n

Paso 1: Definir eventos y comandos

\n\n

Lo primero que hay que hacer es diseñar el dominio. En Rust usamos enumeraciones (enum) para los eventos y estructuras estrictas para los comandos.

\n\n
use serde::{Deserialize, Serialize};\nuse uuid::Uuid;\n\n// Eventos — hechos inmutables\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum OrderEvent {\n    Created { order_id: Uuid, user_id: Uuid, timestamp: i64 },\n    ItemAdded { order_id: Uuid, item: Str                

Blogs

Book Recommendations