La inversión: guardar los hechos, no el resumen
Toda aplicación que has escrito guarda el estado actual y destruye, en cada actualización, el hecho que lo produjo; event sourcing invierte esa decisión y guarda la secuencia inmutable de acontecimientos, dejando el estado actual como una conclusión que siempre se puede volver a derivar. Esta lección desmonta la premisa oculta de la persistencia clásica —que el ahora es el dato y la historia es un lujo opcional— y muestra por qué esa premisa es una pérdida de información irreversible: una actualización destructiva sobrescribe no solo un valor sino el motivo, el instante y el actor que lo cambiaron. A partir de ahí se construye el modelo alternativo: un registro de solo anexado, ordenado, con eventos nombrados en pasado que describen lo que ya ocurrió y no lo que se pide que ocurra, y que una vez escritos nunca se modifican ni se borran, porque corregir la historia significa añadir un hecho nuevo que la enmiende. Al terminar, el lector entiende que la contabilidad de partida doble, el registro de transacciones de una base de datos y el historial de Git son todos la misma idea, y que el estado actual nunca fue la verdad sino su resumen más reciente.
Abre cualquier base de datos que hayas diseñado y mira una fila. Verás un saldo, un carrito, un perfil: el retrato del ahora. Lo que no verás, porque no está, es cómo se llegó hasta ahí. Cada vez que tu código ejecutó una actualización, escribió un valor nuevo encima del anterior y, en ese mismo gesto, borró para siempre el acontecimiento que lo justificaba. La aplicación se quedó con la conclusión y tiró la demostración. Event sourcing propone invertir esa decisión, y la inversión es más profunda de lo que parece a primera vista: en lugar de guardar el estado y derivar la historia cuando alguien la reclama —cosa que ya es imposible—, guarda la historia y deriva el estado cuando hace falta. La secuencia de hechos pasa a ser el dato primario, la única cosa que se persiste, y el estado actual se degrada a lo que siempre fue en realidad: un resumen calculado, una vista conveniente, algo desechable y reconstruible. Esta lección justifica por qué esa inversión no es una excentricidad de arquitectos aburridos, sino el modelo que la contabilidad usa desde el siglo XV, que tu base de datos usa internamente sin contártelo y que Git te enseñó a querer sin que te dieras cuenta.
- Reconocer la actualización destructiva como una pérdida de información irreversible y no como un detalle de implementación.
- Definir el registro de eventos de solo anexado y entender por qué ordenación e inmutabilidad son sus dos garantías centrales.
- Distinguir un evento de un comando y nombrarlos con la disciplina que exige el modelo.
- Situar event sourcing en su linaje: partida doble contable, registro de transacciones y control de versiones.
El estado actual es una conclusión, no una premisa
La persistencia que aprendiste primero descansa sobre una premisa que casi nunca se enuncia: el dato importante es el valor presente, y todo lo demás es contexto prescindible. Esa premisa se materializa en la actualización destructiva. Cuando ejecutas una sentencia que fija el saldo de una cuenta en noventa euros, no estás registrando nada: estás sustituyendo. El valor anterior desaparece, y con él desaparecen tres cosas mucho más valiosas que el número que se fue. Desaparece el motivo —si fue una compra, una devolución, una corrección manual o un error—; desaparece el instante exacto en que ocurrió; y desaparece el actor que lo provocó. Has conservado el qué y has perdido el porqué, el cuándo y el quién. Y como la pérdida es silenciosa y el sistema sigue funcionando, tardas meses en descubrir que la información que ahora te piden nunca llegó a escribirse.
Conviene ser preciso sobre la naturaleza de esa pérdida, porque no es una pérdida cualquiera. En teoría de la información se distingue entre transformaciones reversibles, de las que se puede volver, e irreversibles, que colapsan varios estados de entrada en una única salida y hacen imposible saber de cuál se venía. La asignación destructiva es el ejemplo perfecto de la segunda clase: un saldo de noventa euros pudo llegar a serlo desde cien con una retirada de diez, desde ochenta con un ingreso de diez o desde cero con un ingreso completo, y la fila resultante es idéntica en los tres casos. El sistema conserva el valor y aniquila la preimagen. Ninguna cantidad de análisis posterior recupera lo que la asignación colapsó, del mismo modo que ninguna cantidad de análisis recupera los sumandos de un total.
El síntoma clásico llega en forma de pregunta inocente de negocio. Cuántos usuarios vaciaron el carrito antes de pagar. Cuánto tiempo pasó entre que un pedido se creó y se confirmó. Qué precio tenía este artículo cuando aquel cliente lo compró. Ninguna de esas preguntas se puede responder desde una tabla de estado actual, no porque falte un índice o una columna, sino porque el hecho que las contesta fue sobrescrito. La respuesta habitual del equipo es parchear: añadir una tabla de auditoría, un disparador que copie la fila antigua, un campo updated_at, un registro de cambios paralelo. Cada parche reconoce implícitamente que el modelo original perdía algo, y todos juntos acaban construyendo, mal y a trozos, exactamente aquello que event sourcing propone construir bien y desde el principio.
flowchart LR subgraph Clasico A1[saldo 100] -->|update destructivo| A2[saldo 70] A2 -->|update destructivo| A3[saldo 90] A3 --> A4[solo sobrevive 90] end subgraph EventSourcing B1[CuentaAbierta 100] --> B2[DineroRetirado 30] B2 --> B3[DineroIngresado 20] B3 --> B4[saldo 90 derivado del log] end style A4 fill:#f38ba8,color:#11111b style B4 fill:#a6e3a1,color:#11111b
El registro de solo anexado como fuente de verdad
Ese ciclo de parches tiene además un final predecible que conviene anticipar. Llega un momento en que el historial paralelo es tan rico que alguien propone usarlo como fuente para un informe, y entonces se descubre que no es fiable, porque nadie lo escribió con la disciplina de un almacén primario: faltan cambios, las marcas de tiempo son inconsistentes y hay filas huérfanas de las migraciones antiguas. El equipo ha construido un registro de eventos de baja calidad sin haber obtenido ninguna de sus garantías. Event sourcing no añade una idea nueva a esa historia: propone hacer bien, y desde el principio, lo que el sistema iba a acabar haciendo mal de todas formas.
La estructura que sostiene la inversión es humilde: una lista ordenada a la que solo se puede añadir por el final. Se la llama registro de eventos, o event log, y sus reglas caben en tres líneas. Se anexa siempre al final y nunca en medio. Nada de lo escrito se modifica jamás. Nada de lo escrito se borra jamás. Esas tres reglas parecen limitaciones y son en realidad garantías: convierten el almacén en una estructura de la que se puede razonar, porque un hecho que nunca cambia ni desaparece es un hecho sobre el que se puede construir. La ordenación aporta la segunda garantía, y es la que permite hablar de causalidad: si el evento de retirada precede al de ingreso, reproducirlos en ese orden reproduce el mundo tal como fue, y reproducirlos al revés lo falsea.
type Evento =
| { tipo: 'CuentaAbierta'; saldoInicial: number; en: string }
| { tipo: 'DineroRetirado'; cantidad: number; en: string }
| { tipo: 'DineroIngresado'; cantidad: number; en: string }
// El almacen solo sabe hacer dos cosas: anexar y leer en orden
interface AlmacenDeEventos {
anexar(flujo: string, evento: Evento): Promise<void>
leer(flujo: string): Promise<readonly Evento[]>
}
// No existe actualizar. No existe borrar. Esa ausencia es el diseno.
Merece la pena detenerse en qué significa exactamente que el registro sea la fuente de verdad, porque la frase se usa a la ligera. Significa que si mañana pierdes todas las tablas de tu aplicación salvo el registro, no has perdido información: puedes reconstruirlo todo. Y significa la afirmación recíproca, que es la que muerde: si pierdes el registro, ninguna otra cosa que hayas guardado te devuelve lo perdido, porque todo lo demás es derivado. Esa asimetría reorganiza las prioridades operativas del sistema entero. El registro es lo único que hay que respaldar con paranoia, replicar y verificar; las proyecciones, las cachés y los índices son reconstruibles y merecen exactamente el cuidado que se le da a un artefacto de compilación.
Fíjate en la interfaz: no hay método para actualizar ni para eliminar, y esa ausencia deliberada es toda la arquitectura. El registro se divide en flujos, los streams, uno por cada entidad con identidad propia: una cuenta, un pedido, una partida. Cada flujo tiene su propio orden interno, y dentro de él la secuencia de eventos es la biografía completa de esa entidad, desde que nació hasta ahora. La consecuencia práctica es que el almacén de eventos no necesita ser una tecnología exótica; puede ser una tabla con una clave de flujo, un número de secuencia y una carga útil serializada. Lo que cambia no es el motor, sino la disciplina que le impones.
Queda una pregunta técnica que conviene resolver pronto porque condiciona el diseño: qué ordena los eventos cuando hay varios flujos y varios escritores. Dentro de un flujo el orden es total y trivial, porque cada anexado incrementa un número de secuencia y no hay ambigüedad posible. Entre flujos distintos el orden global es más delicado y a menudo innecesario, porque dos entidades sin relación causal no necesitan que nadie decida cuál ocurrió antes. Esta distinción entre orden local estricto y orden global relajado es la que permite que el modelo escale horizontalmente, y también la que explica por qué las reglas de negocio deben caber dentro de un único flujo, algo que la tercera lección desarrollará bajo el nombre de agregado.
Tu base de datos relacional escribe cada modificación en un registro de escritura anticipada antes de tocar las tablas, y reconstruye su estado desde ese registro tras una caída. Git no guarda archivos sino una cadena de commits que, aplicada en orden, produce el árbol de trabajo. La contabilidad de partida doble prohíbe borrar un asiento erróneo y obliga a compensarlo con otro asiento. Los tres son event sourcing, y los tres eligieron ese modelo por la misma razón: cuando lo que se persiste son hechos inmutables, el estado siempre se puede recuperar, auditar y explicar.
Un evento no es un comando
Hay una consecuencia de la inmutabilidad que desconcierta a todo el mundo la primera vez: qué se hace cuando el evento escrito es sencillamente falso, porque un operador tecleó mal o un error de código emitió un hecho que no ocurrió. La tentación de editarlo es enorme y hay que resistirla, porque un registro que se puede corregir en silencio deja de ser prueba de nada. La respuesta correcta es la de la contabilidad: se anexa un evento nuevo que enmienda al anterior, y el registro conserva ambos, el error y su rectificación. El estado proyectado sale correcto, y además queda documentado que hubo una equivocación, quién la detectó y cuándo, que es información valiosa que un UPDATE habría hecho desaparecer junto con el problema.
// Corregir no es editar: es anexar un hecho que enmienda
await almacen.anexar('cuenta-42', {
tipo: 'DineroIngresado', cantidad: 30, en: '2026-03-01T10:00:00Z',
})
// El importe estaba mal. No se toca el evento anterior.
await almacen.anexar('cuenta-42', {
tipo: 'DineroRetirado', cantidad: 30, en: '2026-03-01T10:04:00Z',
})
La disciplina que hace o rompe este modelo es de vocabulario, y por eso conviene fijarla antes de escribir la primera línea. Un comando es una petición: expresa una intención que aún puede ser rechazada, se nombra en imperativo y su destinatario tiene derecho a decir que no. Un evento es un hecho consumado: expresa algo que ya ocurrió, se nombra en pasado y no admite negociación, porque negar un hecho no lo deshace. La frontera entre ambos es donde vive la lógica de negocio: el sistema recibe RetirarDinero, comprueba las reglas y, si las cumple, emite DineroRetirado. Nombrar un evento en imperativo es el error más común de quien empieza, y no es cosmético: arrastra al equipo a tratar el registro como una cola de tareas pendientes en vez de como un archivo histórico.
Nombrado en pasado
PedidoConfirmado, PrecioCambiado, UsuarioDadoDeBaja. El tiempo verbal no es estilo: recuerda que el hecho ya es irrevocable y que el sistema no puede rechazarlo, solo interpretarlo.
Inmutable para siempre
Un evento mal escrito no se corrige editándolo. Se corrige anexando otro evento que lo enmiende, igual que un contable no borra un asiento sino que emite el contraasiento.
Atado a un flujo
Cada evento pertenece al flujo de una entidad concreta y lleva su número de secuencia. Esa pareja de identidad y orden es la que hace posible reconstruir sin ambigüedad.
Cargado de contexto
Instante, actor, causa y versión del esquema viajan con el evento. Ese metadato es lo que convierte el registro en una explicación y no en una simple lista de números.
Hay una última exigencia que separa los registros útiles de los inservibles: un evento debe capturar la intención del negocio, no la mecánica del almacenamiento. Un flujo lleno de eventos llamados FilaActualizada con el antes y el después de cada columna es técnicamente un registro de eventos y semánticamente basura, porque reproduce el problema que venía a resolver: sabes qué cambió y sigues sin saber por qué. La riqueza del modelo entero depende de que alguien se tome la molestia de nombrar CarritoAbandonado en lugar de EstadoModificado.
Ese trabajo de nombrado no es documentación ni cosmética: es modelado de dominio en su forma más pura. Cuando el equipo discute si el hecho se llama PedidoCancelado o PedidoDevuelto está discutiendo el dominio, no la implementación, y esa conversación produce un vocabulario compartido que sobrevive a cualquier refactorización. Por eso las técnicas de descubrimiento colaborativo que ponen a negocio y desarrollo a colocar eventos en una pared antes de escribir código no son un ritual de consultoría: son la única forma conocida de que los nombres del registro signifiquen algo dentro de tres años.
Tres objeciones que siempre aparecen
Cuando alguien propone esto en una reunión surgen invariablemente las mismas tres objeciones, y conviene tener respuestas honestas, que no son respuestas triunfales. Merece la pena tomárselas en serio en lugar de despacharlas, porque cada una señala un lugar donde el modelo exige trabajo adicional y donde los proyectos que fracasan suelen haber mirado hacia otro lado. La primera es que el registro crece sin límite. Es cierto y no es negociable: un almacén que nunca borra crece de forma monótona, y ese crecimiento es lineal en el número de acontecimientos del negocio, no en el número de lecturas. La respuesta práctica tiene tres partes: el almacenamiento en frío es barato comparado con el valor de la información, los snapshots impiden que el crecimiento afecte al tiempo de lectura, y los flujos que llegan a un final natural —un pedido entregado hace cinco años— se pueden archivar fuera del sistema activo sin destruirlos.
Hay un matiz que suele consolar a quien plantea la objeción: el crecimiento del registro no es proporcional al tráfico sino a los acontecimientos con significado de negocio, que son órdenes de magnitud menos numerosos. Un usuario que consulta cien veces su saldo genera cien lecturas y cero eventos. La confusión nace de imaginar que hay que registrar cada interacción, cuando lo que se registra son los hechos que cambian el dominio, y esos son escasos incluso en sistemas muy visitados. Un negocio con mil operaciones diarias produce menos de cuatro millones de eventos en una década, una cifra que cualquier motor moderno maneja sin despeinarse.
La segunda objeción es más seria y toca la ley. Si nada se borra jamás, qué ocurre con el derecho al olvido que obliga a eliminar los datos personales de una persona cuando lo solicita. La inmutabilidad del registro y esa obligación parecen incompatibles, y la salida canónica es indirecta: los datos personales no se guardan en claro dentro del evento, sino cifrados con una clave propia de cada individuo custodiada aparte. Borrar a esa persona consiste en destruir su clave, lo que deja los eventos intactos en su estructura y su orden pero convierte su contenido personal en ruido irrecuperable. La historia sobrevive como esqueleto verificable y la identidad desaparece de verdad.
La tercera objeción, la del rendimiento, suele ser la más fácil de desmontar. Anexar al final de una secuencia es la operación más favorable que se le puede pedir a un disco: escritura secuencial, sin bloqueos de fila, sin índices que reequilibrar, sin contención entre escritores de flujos distintos. Los sistemas que soportan volúmenes extremos —registros de transacciones, colas distribuidas, motores de series temporales— eligieron todos la misma estructura, y no por elegancia. El coste de este modelo no está en escribir, sino en leer, y para eso están las proyecciones que verás a continuación.
Existe una cuarta objeción que rara vez se formula en voz alta y que es la más decisiva: nadie del equipo ha trabajado así antes. Es una razón legítima para no adoptarlo, y mucho más honesta que las tres técnicas, porque el riesgo de una arquitectura mal entendida supera con creces el beneficio de una bien elegida. La respuesta razonable no es descartar la idea sino reducir su alcance, aplicándola primero a un único concepto del dominio donde el error sea barato y la lección clara. Ese enfoque gradual, que la última lección desarrollará en detalle, es lo que separa a los equipos que aprenden el patrón de los que lo sufren.
Las tres objeciones comparten una estructura que conviene reconocer, porque volverás a encontrarla cada vez que discutas arquitectura. Ninguna de ellas es falsa: el registro crece, el borrado legal es un problema real y las lecturas se complican. Lo que fallan es la comparación implícita, porque miden el coste del modelo nuevo contra un modelo antiguo idealizado que en realidad también paga esos costes, solo que de forma dispersa e invisible. El sistema clásico también acumula tablas de historial que crecen, también tiene que borrar datos personales repartidos por doce sitios y también sufre para responder consultas complejas. La diferencia no está en si se paga, sino en si se paga de forma explícita, centralizada y con un modelo mental claro, o de forma difusa y en el sitio menos oportuno.
Si al modelar dudas entre dos nombres para el mismo hecho, casi siempre es señal de que hay dos hechos distintos que el modelo actual confunde. SuscripcionCancelada y SuscripcionCaducada producen el mismo estado y significan cosas opuestas: una es una decisión del cliente y la otra un vencimiento administrativo. Distinguirlas cuesta cinco minutos hoy y desbloquea, dentro de dos años, la única pregunta que de verdad importaba sobre la retención.
Hay una asimetría lógica en el corazón de esta lección, y una vez la ves no puedes dejar de verla. Desde la secuencia completa de hechos puedes derivar el estado actual siempre que quieras, con una operación mecánica y determinista. Desde el estado actual no puedes derivar los hechos jamás, porque la información sencillamente no está: la destruyó la sentencia que la sobrescribió. La relación entre ambos no es simétrica sino jerárquica, y durante toda tu carrera has estado persistiendo el lado equivocado de esa jerarquía. Guardabas el resumen y tirabas el original. La consecuencia es que cada actualización destructiva es una decisión irreversible tomada sin saberlo: alguien, hace tres años, escribió una línea que sobrescribía un campo y con ella decidió que la pregunta que tu jefe de producto hace hoy no tiene respuesta posible. Event sourcing no es una técnica de rendimiento ni un patrón para sistemas grandes; es una postura epistemológica sobre qué merece llamarse verdad en un sistema de información. Sostiene que la verdad es el conjunto de cosas que ocurrieron, que ese conjunto solo crece, y que cualquier otra cosa que tu aplicación muestre —el saldo, el inventario, el ranking— es una interpretación derivada, revisable y desechable de esa verdad. Cuando interiorizas eso, el estado actual deja de ser sagrado y se vuelve barato: si está mal, lo tiras y lo recalculas. Es exactamente el mismo cambio mental que hizo la contabilidad hace seiscientos años, y por eso ningún contable del mundo aceptaría trabajar con una tabla que se sobrescribe.
- Toma una tabla de un proyecto tuyo y enumera cinco preguntas de negocio que no puedas contestar con ella. Señala en cada una qué actualización destructiva borró la respuesta.
- Reescribe esa tabla como un registro de eventos: nombra al menos seis eventos en pasado que capturen intención de negocio y no mecánica de columnas.
- Implementa la interfaz
AlmacenDeEventosdel ejemplo sobre un array en memoria y comprueba que no puedes añadirle un método de borrado sin romper el modelo. - Para cada evento, decide qué comando lo precede y quién tiene derecho a rechazarlo. Explica dónde queda exactamente la lógica de negocio.
- Modela una corrección: un importe se registró mal. Escribe el evento compensatorio en lugar de editar el original y justifica por qué es preferible.
- Explica en tres frases por qué el historial de Git y el registro de escritura anticipada de una base de datos son el mismo patrón que acabas de construir.
- Toma un evento tuyo que contenga datos personales y diseña dónde vivirían la clave y el texto cifrado para que destruir la clave equivalga a olvidar a esa persona.
- Estima el crecimiento anual del registro de tu dominio en número de eventos y en bytes, y decide si el volumen justifica archivar flujos cerrados.
- Busca dos nombres de evento que produzcan el mismo estado y signifiquen cosas distintas en tu dominio, y explica qué pregunta desbloquea distinguirlos.