¿Qué es el event sourcing — almacenar eventos en vez de estado — y qué se gana y qué se pierde?
En vez de sobrescribir una fila, el event sourcing agrega lo que ocurrió y reconstruye el estado actual reproduciendo el historial -- el registro de auditoría se convierte en la fuente de verdad.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Qué es el event sourcing — almacenar eventos en vez de estado — y qué se gana y qué se pierde?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#De almacenar estado a almacenar historia
Un sistema CRUD convencional almacena el estado actual: una fila guarda el balance de un cliente, y cada operación lo sobrescribe en el lugar. El valor anterior desaparece en el momento en que el UPDATE confirma -- si necesitás saber cómo el balance llegó a su valor actual, necesitás un log de auditoría separado, y ese log es tan completo como quien se haya acordado de escribirlo. El event sourcing invierte esto: en vez de almacenar el estado, almacenás la secuencia de eventos que lo produjeron, y el estado actual pasa a ser un valor derivado, calculado reproduciendo (haciendo fold sobre) esa secuencia.
Un agregado en un sistema con event sourcing se reconstruye partiendo de un estado vacío y aplicando cada evento que le ocurrió, en orden. Suena costoso, y a cierta escala lo es, pero el estado en sí nunca es el artefacto durable -- los eventos sí lo son. Siempre podés descartar el estado actual y recalcularlo, lo cual es una garantía extraña pero poderosa.
#Un agregado con event sourcing, de punta a punta
La forma más clara de verlo es una cuenta bancaria pequeña: los comandos se validan contra el estado en memoria producido por el replay, y un comando válido produce un evento nuevo en vez de mutar un campo directamente.
public sealed interface Event permits AccountOpened, MoneyDeposited, MoneyWithdrawn, AccountClosed {}public record AccountOpened(String accountId) implements Event {}public record MoneyDeposited(String accountId, long amountCents) implements Event {}public record MoneyWithdrawn(String accountId, long amountCents) implements Event {}public record AccountClosed(String accountId) implements Event {}public final class BankAccount {private String accountId;private long balanceCents;private boolean closed;// Reconstruye el estado reproduciendo la historia -- nunca se lee// desde una columna de 'estado actual', solo desde los eventos que lo produjeron.public static BankAccount replay(List<Event> history) {BankAccount account = new BankAccount();for (Event event : history) {account.apply(event);}return account;}private void apply(Event event) {if (event instanceof AccountOpened e) {this.accountId = e.accountId();this.balanceCents = 0;this.closed = false;} else if (event instanceof MoneyDeposited e) {this.balanceCents += e.amountCents();} else if (event instanceof MoneyWithdrawn e) {this.balanceCents -= e.amountCents();} else if (event instanceof AccountClosed e) {this.closed = true;}}// Manejo de comandos: valida contra el estado reproducido, y luego// produce un evento nuevo en vez de mutar una columna directamente.public MoneyWithdrawn withdraw(long amountCents) {if (closed) {throw new IllegalStateException("account is closed");}if (amountCents > balanceCents) {throw new IllegalStateException("insufficient funds");}MoneyWithdrawn event = new MoneyWithdrawn(accountId, amountCents);apply(event);return event;}}
Nada acá lee o escribe una columna balance. withdraw solo produce un evento después de validarlo contra un estado que a su vez se derivó de eventos previos -- el event store es lo único que tiene que sobrevivir a un reinicio.
#Qué se gana con event sourcing
- Un registro de auditoría completo por construcción. No agregás logging alrededor de los cambios de estado -- los eventos son el log, y es imposible que se desincronice de la realidad.
- Viajar en el tiempo. Reconstruir el estado tal como estaba en cualquier momento pasado significa reproducir eventos solo hasta esa marca de tiempo -- útil para depurar ('cómo estaba esta cuenta antes del cargo disputado') y para cumplimiento normativo.
- Nuevos modelos de lectura gratis, en retrospectiva. Si aparece una necesidad de reporting nueva el año que viene, podés construir una proyección totalmente nueva y poblarla reproduciendo eventos que ya tenías, sin haber anticipado esa necesidad desde el día uno.
- Integración orientada a eventos de forma natural. Los sistemas downstream que quieren reaccionar a
MoneyWithdrawnse suscriben a algo que ya producís, no a un diff inferido entre el antes y el después.
#Qué cuesta
| Aspecto | CRUD tradicional | Event sourcing |
|---|---|---|
| Fuente de verdad | Estado actual de la fila | Secuencia ordenada de eventos inmutables |
| Historia | Se pierde salvo que se loguee aparte | Incluida por construcción |
| Estado en un punto del tiempo | Difícil, necesita versionado extra | Natural -- reproducir hasta una marca de tiempo |
| Consultas ad hoc | SQL directo contra el estado actual | Generalmente necesita una proyección (a menudo junto con CQRS) |
| Corregir datos mal cargados | UPDATE sobre la fila | Agregar un evento compensatorio -- los eventos son inmutables |
| Crecimiento del almacenamiento | Acotado por la cantidad de entidades | Crece con cada cambio de estado; eventualmente necesita snapshots |