https://a.storyblok.com/f/270183/1368x665/a1e36438ef/25jul_dev_blog_event-sourcing.png

¿Debería Event Source?

Publicado el July 17, 2025

Tiempo de lectura: 7 minutos

Si la década de 2020 ha mostrado algo en tecnología, la palabra "cadena de bloques" tiene que estar ahí arriba en una miríada de interpretaciones positivas y negativas. Si tomamos los aspectos "buenos" o "útiles" de una cadena de bloques, a menudo están relacionados con el aprovechamiento de las ventajas de su naturaleza inmutable. La historia es la historia.

Así que tienes una idea y piensas "blockchain sería perfecto para esto", pero también tienes que reconocer el elefante en la habitación: web3, blockchains y criptotecnología son tecnologías difíciles de integrar. Si vienes de un enfoque tradicional en ingeniería de software, ¡buenas noticias! Hay un patrón de diseño de datos que ha existido durante bastante tiempo (en particular en campos como las finanzas) que tiene un enfoque similar para rastrear el estado histórico de los datos. En este artículo, vamos a explorar Event Sourcing.

Los orígenes de la contratación de eventos

Image showing generic line chart data being mappedAll Together Now: Data is the new OilEl concepto de Event Sourcing nació de la necesidad de registrar los datos de las transacciones y las transformaciones que sufrían a lo largo del tiempo. Instituciones como los bancos necesitarían poder ver una "instantánea" del aspecto de una entidad en un momento preciso. Utilizando el concepto de diseño orientado al dominio (DDD), el nacimiento más formal del Event Sourcing se produjo con el lanzamiento de segregación de responsabilidad de consulta de comandos (o CQRS), que se utiliza habitualmente en entornos basados en Microsoft. Esta segregación no se centra en los datos, sino en las acciones que se producen cuando cambia su estado. Estas acciones se registran como entidades inmutables, y es este patrón el que define el Event Sourcing.

Una aplicación típica en finanzas

Photo of paperwork showing generic stock market press reportingIt's a Bull Market, but how do you audit it?Tomemos, por ejemplo, algo tan sencillo como el saldo de una Account. En una configuración tradicional, tendríamos algo así como una base de datos relacional y una aplicación que extrajera datos de esa cuenta con una sentencia SQL preparada. La cifra del saldo podría obtenerse bien si las transacciones mantienen el saldo de la cuenta en estado en cada transacción, o bien si la aplicación intenta agregarlo todo. Aquí es donde entra en juego Event Sourcing: ¿qué pasa si tienes millones de filas y quieres ver el estado de un saldo en un momento exacto del pasado? Tienes que esperar que los datos transaccionales sean todos sean correctos, y que no se haya producido una migración descendente, o una interrupción justo en el momento exacto en que se intenta llevar una cifra de saldo total en un registro de transacción.

Con Event Sourcing, se "reproduce la historia" porque los datos que se mantienen son sobre los cambios en el estado de los datos. Por lo tanto, para aplicar esto, usted crearía algunos modelos de eventos personalizados con nombres apropiados, tales como:

class AccountCreatedEvent extends Event

class AccountDepositEvent extends Event

class AccountWithdrawEvent extends Event

Esta serie de eventos se denomina un flujo. La forma de manejar la obtención del balance es con un agregador, que se llama a proyector. Una clase proyector tomaría un argumento, luego leería los eventos (consultados previamente por una clave primaria como el ID de Account) que se le pasan para calcular el estado:

class AccountBalanceProjector {

    public static int projectBalance(List<Event> events) {

        int balance = 0;
        boolean accountCreated = false;

        for (Event event : events) {
            if (event instanceof AccountCreatedEvent) {
                balance = ((AccountCreatedEvent) event).initialBalance;
                accountCreated = true;
            } else if (accountCreated && event instanceof AccountDepositEvent) {
                balance += ((AccountDepositEvent) event).amount;
            } else if (accountCreated && event instanceof AccountWithdrawEvent) {
                balance -= ((AccountWithdrawEvent) event).amount;
            }
        }
        return balance;
    }
}

Y ahí lo tenemos. Tendrías un método en tu Object-Relational Mapper (ORM) que recuperaría todos los eventos para un específico accountIdluego pasaría esos eventos a AccountBalanceProjector.projectBalance(events)y regresa tu balance. No ha sacado el balance que ha sido pre-creado en algún lugar, lo ha recreado.

Uso de Event Sourcing con datos de Vonage

Graphic design image showing developers hard at workControlling Data Can Be TrickyVeamos un ejemplo utilizando los datos generados al utilizar Vonage. Vamos a examinar la Voice API, que genera un webhook de estado de evento cada vez que se produce una acción durante una llamada de voz. De acuerdo con la documentación de Voice API Webhooksse pueden dar los siguientes estados

Si introduzco estos estados en la IA y le pido al prompt que genere mis clases, puedo conseguir un poco de magia automatizada para generar mi lista de eventos:

public class CallStarted extends BaseVoiceEvent {
    public CallStarted(String callId) { super(callId); }

    @Override public String getName() { return "started"; }
}

public class CallRinging extends BaseVoiceEvent {
    public CallRinging(String callId) { super(callId); }

    @Override public String getName() { return "ringing"; }
}

public class CallAnswered extends BaseVoiceEvent {
    private String answerType;

    public CallAnswered(String callId, String answerType) {
        super(callId);

        this.answerType = answerType;
    }

    @Override public String getName() { return "answered"; }

    @Override public Map<String, Object> toMap() {
        Map<String, Object> map = super.toMap();
        map.put("answer_type", answerType);
        return map;
    }
}

public class CallBusy extends BaseVoiceEvent {
    public CallBusy(String callId) { super(callId); }

    @Override public String getName() { return "busy"; }
}

public class CallCancelled extends BaseVoiceEvent {
    public CallCancelled(String callId) { super(callId); }

    @Override public String getName() { return "cancelled"; }
}

public class CallUnanswered extends BaseVoiceEvent {
    public CallUnanswered(String callId) { super(callId); }

    @Override public String getName() { return "unanswered"; }
}

public class CallDisconnected extends BaseVoiceEvent {
    public CallDisconnected(String callId) { super(callId); }

    @Override public String getName() { return "disconnected"; }
}

public class CallRejected extends BaseVoiceEvent {
    public CallRejected(String callId) { super(callId); }

    @Override public String getName() { return "rejected"; }
}

public class CallFailed extends BaseVoiceEvent {
    public CallFailed(String callId) { super(callId); }

    @Override public String getName() { return "failed"; }
}

public class CallHumanMachineDetected extends BaseVoiceEvent {
    private String detectionResult;

    public CallHumanMachineDetected(String callId, String detectionResult) {
        super(callId);

        this.detectionResult = detectionResult;
    }

    @Override public String getName() { return "human_machine"; }

    @Override public Map<String, Object> toMap() {
        Map<String, Object> map = super.toMap();
        map.put("detection_result", detectionResult);

        return map;
    }
}

public class CallTimeout extends BaseVoiceEvent {
    public CallTimeout(String callId) { super(callId); }

    @Override public String getName() { return "timeout"; }
}

public class CallCompleted extends BaseVoiceEvent {
    public CallCompleted(String callId) { super(callId); }

    @Override public String getName() { return "completed"; }
}

// etc etc, truncado por razones de longitud

Verás que estos Eventos se extienden a partir de una clase base, que definimos como abstracta:

import java.time.Instant;

import java.util.HashMap;

import java.util.Map;

public abstract class BaseVoiceEvent {
    protected String callId;
    protected Instant timestamp;

    public BaseVoiceEvent(String callId) {
        this.callId = callId;

        this.timestamp = Instant.now();
    }

    public BaseVoiceEvent(String callId, Instant timestamp) {
        this.callId = callId;

        this.timestamp = timestamp;
    }

    public abstract String getName();

    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("event", getName());
        map.put("call_id", callId);
        map.put("timestamp", timestamp.toString());

        return map;
    }
}

Ahora tienes la estructura inicial de una arquitectura de Event Sourcing. Los pasos finales serían tener controladores para manejar la creación de eventos en un punto final HTTP a su aplicación, y luego un Proyector de Estado de Llamada. Ese proyector, similar al ejemplo que tenemos anteriormente, sería capaz de tomar un flujo de eventos, tomar una marca de tiempo opcional, que luego calcula el estado correcto de la callID a la marca de tiempo que ha dado (o a la más reciente por defecto).

¿Cuáles son las ventajas de la contratación de eventos?

  • La arquitectura basada en eventos puede refactorizarse. Debido a que el sistema está desacoplado de los datos de estado que entran, puede volver a ejecutar sus nuevos controladores con una nueva lógica (por ejemplo, para manejar un nuevo tipo de evento) y reconstruir sus flujos de datos para la proyección.

  • Audit trails completos independientemente del tiempoEsto es muy importante para los sectores muy regulados, como hemos visto en sectores como el financiero y el sanitario.

  • Separación de lecturas y escrituras: Utilizando el mencionado CQRS, se divide la forma en que se accede a los datos y se escriben. Cuando se ajusta una aplicación que requiere una elevada entrada/salida de datos, se pueden escalar aspectos separados de la arquitectura en consecuencia.

¿Cuáles son las desventajas de la contratación de eventos?

No podría llamarme un ingeniero sensato sin decir cuáles son los inconvenientes, y son bastante grandes

  • Compatibilidad con versiones anteriores: He mencionado lo rápido que puede ser introducir nuevos cambios en los modelos y volver a ejecutar los controladores de eventos, pero no cuando algo queda obsoleto. Es necesario pensar en el manejo de eventos, versiones semánticas y comportamientos, y muy posiblemente conducir a colecciones considerables de datos antiguos que necesitan ir al almacenamiento, mientras que tener que hacer complejos procedimientos ETL.

  • Complejidad del sistema: Este es el mayor obstáculo a superar. He trabajado en sistemas de eventos que estaban escalando en el pasado, y la mayor parte de esta complejidad es similar a los microservicios. Si no has arquitecturado tu sistema de eventos correctamente desde el principio, entonces te puedo asegurar que un enum cambiante está a la espera de hacer tropezar todo tu sistema

  • Depuración: Una de las mayores frustraciones que he encontrado en el diseño de software es cuando no se dispone de las herramientas de depuración adecuadas. A menudo he hablado sobre mi consternación por no tener xdebug de Derick Rethan como parte de una pila de PHP; simplemente no veo cómo alguien puede codificar sin ella, en lugar de matar el tiempo de ejecución y volcar arrays o cadenas individuales. Del mismo modo, la depuración de microservicios, que me lleva a entornos de Event Sourcing: para depurar flujos con precisión, es probable que tenga que escribir herramientas personalizadas adicionales en la parte superior de su sistema para cuando las cosas van mal.

  • Almacenamiento: Esto puede ser un poco obvio, pero en lugar de tener una fuente de verdad (en nuestro ejemplo, era almacenar los Webhooks de la Voice API de Vonage), ahora tienes varias tablas adicionales que tienen muchos datos adicionales que se crean sobre los datos de origen.

Hazlo a tu manera

Por lo que hemos visto, la estructura vainilla para hacer esto es bastante compleja. La buena noticia es que existen librerías que nos ayudan a implementarlo. Aquí están mis recomendaciones para cada lenguaje backend:

Conclusión

Al igual que con cada nuevo patrón de ingeniería que emerge, a menudo soy cínico sobre la utilidad de estas cosas en la práctica. He visto charlas sobre arquitectura hexagonal, y aplicaciones frontales React con patrones de herencia inversa, y simplemente no puedo entender por qué los desarrolladores querrían quieren este nivel de complejidad. Sin embargo, en el caso de Event Sourcing, creo que es una situación muy diferente. Sí, es difícil de implementar y arquitecturar, pero como con todo, ¿cuál es tu caso de uso? Si usted está escribiendo en un mercado vertical que requiere altos niveles de escrutinio de datos, a continuación, establecer con Event Sourcing desde el principio va a salvar su negocio de potencialmente grandes dolores de cabeza en el camino.

¿Tienes alguna pregunta o algo que compartir? Únete a la conversación en Slack de la comunidad de Vonagey mantente actualizado con el Boletín para desarrolladoressíguenos en X (antes Twitter)suscríbete a nuestro canal de YouTube para ver tutoriales en video, y sigue la página de página para desarrolladores de Vonage en LinkedInun espacio para que los desarrolladores aprendan y se conecten con la comunidad. Mantente conectado, comparte tu progreso y entérate de las últimas noticias, consejos y eventos para desarrolladores.

Compartir:

https://a.storyblok.com/f/270183/400x385/12b3020c69/james-seconde.png
James SecondePromotor senior de desarrollo PHP

Actor de formación con una disertación sobre la comedia, llegué al desarrollo de PHP a través de la escena de las reuniones. Puedes encontrarme hablando y escribiendo sobre tecnología, o tocando/comprando discos raros de mi colección de vinilos.