GH GambleHub

Event Sourcing: fundamentos

Qué es Event Sourcing

Event Sourcing (ES) es una forma de almacenar el estado de los objetos de dominio, no como una «cadena actual», sino como un registro de eventos inmutable que describe todo lo que ha ocurrido. El estado actual de un agregado se obtiene mediante la convolución (replay) de sus eventos, y cualquier representación a leer se construye como proyecciones sobre este registro.

Las principales consecuencias son:
  • La historia es la «fuente primaria de la verdad», el estado es la proyección de la historia.
  • Cualquier estado se puede volver a reproducir, verificar y explicar (auditoría).
  • Agregar nuevas vistas y análisis no requiere migraciones de «instantáneas» antiguas: basta con perder eventos.

Términos básicos

Un agregado es una unidad de consistencia de dominio con invariantes claros (Orden, Pago, Balance de usuario).
Un evento es un hecho inmutable que ocurrió en el pasado ('payment. authorized`, `order. shipped`).
Event Store es un registro Append-Only que proporciona el orden de los eventos dentro de la unidad.
La versión del agregado es el número del último evento aplicado (para optimistic concurrency).
Snapshot es un estado de extracción periódica para acelerar la convolución.
Proyección (read-model) es una vista materializada para lectura/búsqueda/reporting (a menudo asíncrona).

Cómo funciona (flujo de comandos → eventos → proyecciones)

1. El cliente envía un comando ('CapturePayment', 'PlaceOrder').
2. El agregado valida invariantes y, si todo está cerca, da lugar a eventos.
3. Los eventos se agregan atomicamente al Event Store con verificación de versión (optimistic concurrency).
4. Los procesadores de proyección están suscritos al flujo de eventos y actualizan los modelos de lectura.
5. Al cargar la unidad para el siguiente comando, el estado se restaura: snapshot (si lo hay) → eventos después del snapshot.

Diseño de eventos

Atributos obligatorios (núcleo)

json
{
"event_id": "uuid",
"event_type": "payment. authorized. v1",
"aggregate_type": "Payment",
"aggregate_id": "pay_123",
"aggregate_version": 5,
"occurred_at": "2025-10-31T10:42:03Z",
"payload": { "amount": 1000, "currency": "EUR", "method": "card" },
"meta": { "trace_id": "t-abc", "actor": "user_42" }
}
Recomendaciones:
  • Nomenclatura: 'domain. action. v{major}`.
  • Aditividad: los nuevos campos son opcionales, sin cambiar el significado de los antiguos.
  • Minimalismo: solo hechos, sin duplicar datos fácilmente recuperables.

Contratos y esquemas

Fije los circuitos (Avro/JSON Schema/Protobuf) y compruebe la compatibilidad con el CI.
Para los cambios «rompedores», una nueva versión mayor del evento y la publicación paralela 'v1 '/' v2' para el período de migración.

Acceso competitivo: optimistic concurrency

Regla: sólo es posible escribir nuevos eventos si 'expected _ version = = current_version'.

Pseudocódigo:
pseudo load: snapshot(state, version), then apply events > version new_events = aggregate. handle(command)
append_to_store(aggregate_id, expected_version=current_version, events=new_events)
//if someone has already written an event between load and append, the operation is rejected -> retray with reload

Así garantizamos la integridad de los invariantes sin transacciones distribuidas.

Snapshots (aceleración de la convolución)

Haga un snapshot cada N de eventos o por temporizador.
Храните `snapshot_state`, `aggregate_id`, `version`, `created_at`.
Compruebe siempre y póngase al día con los eventos después del apretón (no confíe sólo en el yeso).
Retire los snapshots para que se puedan volver a crear desde el registro (no guarde campos «mágicos»).

Proyecciones y CQRS

ES se combina naturalmente con CQRS:
  • Modelo escrito = agregados + Tienda de eventos.
  • Modelos de lectura = proyecciones actualizadas por eventos (tarjetas Redis, OpenSearch para búsqueda, ClickHouse/OLAP para informes).
  • Las proyecciones son idempotentes: volver a procesar el mismo 'event _ id' no cambia el resultado.

Evolución de los circuitos y compatibilidad

Additive-first: agregue campos; no cambie de tipo/semántica.
Para cambios complejos: libere nuevos tipos de eventos y escriba migradores de proyecciones.
Mantenga una entrada doble ('v1' + 'v2') durante el periodo de transición y retire 'v1' cuando todas las proyecciones estén listas.

Seguridad, PII y «derecho al olvido»

La historia a menudo contiene datos sensibles. Enfoques:
  • Minimice la PII en los eventos (identificadores en lugar de datos, piezas en lados protegidos).
  • Encriptación: cifrar los campos y, cuando se solicite la eliminación, destruir la clave (el evento permanece, pero los datos no están disponibles).
  • Eventos editoriales: 'user. piiredacted. v1 'con la sustitución de campos sensibles en las proyecciones (la historia conserva el hecho de editar).
  • Directivas de retención: para algunos dominios, parte de los eventos se pueden archivar en el almacén WORM.

Rendimiento y zoom

Partición: el orden es importante dentro del agregado - lote por 'aggregate _ id'.
Inicio en frío: snapshots + soldadura periódica «sellando».
Apéndice de batalla: agrupe los eventos con una sola transacción.
Backpressure y DLQ para procesadores de proyección; mida el valor (tiempo y número de mensajes).
Indexación de la tienda de eventos: acceso rápido por '(aggregate_type, aggregate_id)' y por tiempo.

Pruebas de especialización para agregados: script «comandos → eventos esperados».
Projection tests: alimenta el flujo de eventos y comprueba el estado/índices materializados.
Pruebas de replayability: volver a envolver las proyecciones «desde cero» en el stand - asegúrese de que el total coincida.
Chaos/latency: inyectar retrasos y tomas, comprobar la idempotencia.

Ejemplos de dominio

1) Pagos

Eventos: 'pago. initiated`, `payment. authorized`, `payment. captured`, `payment. refunded`.
Invariantes: no se puede 'capture' sin 'authorized'; las cantidades no son negativas; la moneda no cambia.
Proyecciones: «tarjeta de pago» (KV), búsqueda de transacciones (OpenSearch), informes (OLAP).

2) Pedidos (e-commerce)

Eventos: 'order. placed`, `order. paid`, `order. packed`, `order. shipped`, `order. delivered`.
Invariantes: transiciones de estado por diagrama de estado; es posible cancelar hasta 'shipped'.
Proyecciones: lista de pedidos del usuario, SLA-dashboards por estado.

3) Balances (finanzas/iGaming)

Eventos: 'balance. deposited`, `balance. debited`, `balance. credited`, `balance. adjusted`.
Invariante rígido: el equilibrio no desaparece <0; los comandos son idempotentes por 'operation _ id'.
Las operaciones críticas se leen directamente desde la unidad (consistencia estricta), IU desde la proyección (eventual).

Estructura típica de la tienda de eventos (versión con DB)

events

`event_id (PK)`, `aggregate_type`, `aggregate_id`, `version`, `occurred_at`, `event_type`, `payload`, `meta`

Índice: '(aggregate_type, aggregate_id, versión)'.

snapshots

`aggregate_type`, `aggregate_id`, `version`, `state`, `created_at`

Índice: '(aggregate_type, aggregate_id)'.

consumers_offsets

'consumer _ id', 'event _ id '/' position', 'updated _ at' (para proyecciones y retlay).

Preguntas frecuentes (FAQ)

¿Es obligatorio usar ES en todas partes?
No. ES es útil cuando la auditoría, los invariantes complejos, la reproducibilidad y las diferentes representaciones de datos son importantes. Para un simple CRUD, es redundante.

¿Qué hay de las solicitudes de «estado actual»?
O lee desde la proyección (rápida, eventual) o desde la unidad (más cara, pero estrictamente). Las operaciones críticas generalmente utilizan una segunda ruta.

¿Kafka/corredor de streaming necesita?
Event Store es la fuente de la verdad; el corredor es conveniente para la distribución de eventos a proyectores y sistemas externos.

¿Qué hacer con el «derecho al olvido»?
Minimizar PII, cifrar campos sensibles y aplicar encriptación/edición en las proyecciones.

¿Cómo migrar los datos antiguos?
Escriba un script de generación retrospectiva de eventos («re-heistori») o comience con «estado-como-es» y publique eventos sólo para nuevos cambios.

Antipattern

Event Sourcing «por hábito»: complica el sistema sin beneficio de dominio.
Fat events: inflado payload's con PII y dobletes - frenos y problemas de cumplimiento.
Falta de concurrencia optimística: pérdida de invariantes en las carreras.
Proyecciones no reproducibles: no hay réplica/snapshots → fixes manuales.
CDC crudos como eventos de dominio: fugas de circuitos de DB y conectividad rígida.
Mezcla de eventos internos y de integración: hacia fuera publique un «escaparate» estable.

Lista de verificación para producción

  • Se han definido agregados, invariantes y eventos (nombres, versiones, esquemas).
  • Event Store proporciona orden dentro de la unidad y optimistic concurrency.
  • Se incluyen los snapshots y el plan para volver a crearlos.
  • Las proyecciones son idempotentes, hay DLQ y métricas de lagas.
  • Los esquemas se validan en CI, la política de versiones está documentada.
  • La PII está minimizada, los campos están cifrados, hay una estrategia de «olvido».
  • Se ha probado un repleo de proyecciones en el stand; hay un plan de recuperación ante desastres.
  • Dashboards: Velocidad de Apéndice, Magna de Proyecciones, Errores de Aplicación, Proporción de Retratos.

Event Sourcing hace de la historia del sistema un artefacto de primera clase: captamos los hechos, a partir de ellos reproducimos el estado y construimos libremente cualquier representación. Esto permite la auditoría, la resistencia al cambio y la flexibilidad de la analítica, sujeta a la disciplina en los esquemas, el control competitivo y el trabajo competente con datos sensibles.

Contact

Póngase en contacto

Escríbanos ante cualquier duda o necesidad de soporte.¡Siempre estamos listos para ayudarle!

Iniciar integración

El Email es obligatorio. Telegram o WhatsApp — opcionales.

Su nombre opcional
Email opcional
Asunto opcional
Mensaje opcional
Telegram opcional
@
Si indica Telegram, también le responderemos allí además del Email.
WhatsApp opcional
Formato: +código de país y número (por ejemplo, +34XXXXXXXXX).

Al hacer clic en el botón, usted acepta el tratamiento de sus datos.