CQRS y Event Sourcing en Go: arquitectura para sistemas complejos

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

Cuando el CRUD habitual ya no es suficiente: introducción a CQRS y Event Sourcing

\n

Imagine que está construyendo un sistema de reserva de boletos. Todo es sencillo mientras hay diez usuarios. Pero cuando se convierten en mil, y luego en un millón, comienzan los problemas: las bases de datos se bloquean, las consultas entran en conflicto y el historial de cambios es imposible de recuperar. ¿Le suena familiar?

\n

En el mundo del desarrollo en Go, donde el rendimiento y el paralelismo lo son todo, dos patrones arquitectónicos ayudan a resolver estos problemas: CQRS (Command Query Responsibility Segregation) y Event Sourcing. Hoy los analizaremos en la práctica, con código, ejemplos y escenarios de uso reales.

\n\n

¿Qué es CQRS en términos sencillos?

\n

CQRS es la separación de responsabilidades entre comandos (escritura) y consultas (lectura). En el CRUD clásico, un único modelo de datos se encarga tanto de la creación como de la lectura. En CQRS, creamos dos modelos separados:

\n
    \n
  • Modelo de comando — se encarga de cambiar el estado (crear, actualizar, eliminar)
  • \n
  • Modelo de consulta — se encarga solo de la lectura de datos
  • \n
\n

Esto permite optimizar cada lado de forma independiente: la escritura puede ser estrictamente validada, y la lectura puede ser desnormalizada y rápida. En Go, esto es especialmente eficaz gracias a las gorutinas y los canales.

\n\n

Ejemplo sencillo de CQRS en Go

\n

Vamos a crear un sistema básico para la gestión de pedidos. Empezamos con los comandos:

\n\n
type CreateOrderCommand struct {\n    OrderID   string\n    UserID    string\n    ProductID string\n    Amount    float64\n}\n\ntype CommandHandler struct {\n    repo *OrderRepository\n}\n\nfunc (h *CommandHandler) HandleCreateOrder(ctx context.Context, cmd CreateOrderCommand) error {\n    order := Order{\n        ID:        cmd.OrderID,\n        UserID:    cmd.UserID,\n        ProductID: cmd.ProductID,\n        Amount:    cmd.Amount,\n        Status:    "pending",\n        CreatedAt: time.Now(),\n    }\n    return h.repo.Save(ctx, order)\n}
\n\n

Ahora creamos un modelo separado para la lectura:

\n\n
type OrderQuery struct {\n    repo *ReadRepository\n}\n\ntype OrderView struct {\n    OrderID    string\n    UserName   string\n    ProductName string\n    Total      float64\n    Status     string\n}\n\nfunc (q *OrderQuery) GetOrderSummary(ctx context.Context, orderID string) (*OrderView, error) {\n    return q.repo.GetOrderView(ctx, orderID)\n}
\n\n

Observe que en OrderView ya hay datos desnormalizados (nombre de usuario, nombre del producto) que se recopilan de diferentes fuentes. Esto acelera la lectura decenas de veces.

\n\n

Event Sourcing: registramos no el estado, sino los eventos

\n

Event Sourcing cambia el enfoque del almacenamiento de datos: en lugar de almacenar el estado actual de un objeto, almacenamos la secuencia de eventos que llevaron a ese estado. Esto otorga un superpoder: el historial completo de cambios y la capacidad de «rebobinar» el estado.

\n\n

Implementación de Event Sourcing en Go

\n

Empezamos con los tipos básicos de eventos:

\n\n
type Event struct {\n    ID        string\n    AggregateID string\n    Type      string\n    Data      []byte\n    Timestamp time.Time\n    Version   int\n}\n\ntype EventStore interface {\n    Save(ctx context.Context, events []Event) error\n    Load(ctx context.Context, aggregateID string) ([]Event, error)\n}
\n\n

Ahora implementamos el agregado — el objeto que restaura su estado a partir de los eventos:

\n\n
type OrderAggregate struct {\n    ID     string\n    Status string\n    Items  []OrderItem\n    Version int\n}\n\nfunc (a *OrderAggregate) Apply(event Event) {\n    switch event.Type {\n    case "OrderCreated":\n        var data OrderCreatedData\n        json.Unmarshal(event.Data, &data)\n        a.ID = data.OrderID\n        a.Status = "created"\n    case "OrderConfirmed":\n        a.Status = "confirmed"\n    case "OrderShipped":\n        a.Stat                

Blogs

Book Recommendations