Empezamos la quinta entrega de esta serie de post de patrones de arquitectura. Por si os habéis perdido, algo os dejo los artículos anteriores:

Hoy, seguimos hablando de comunicación y coordinación entre microservicios, y nos centraremos en Event Sourcing y arquitectura orientada a eventos (EDA). Más adelante, cerramos esta con CQRS (Command Query Responsibility Segregation), BFF (Backend for Frontend) y Outbox.

No terminaremos ahí, ya que queda mucho contenido para siguientes publicaciones donde veremos muchos más patrones.

Event Sourcing

El patrón Event Sourcing es una técnica de diseño de software que consiste en almacenar un registro secuencial de todos los cambios que ocurren en el estado de una aplicación como una secuencia de eventos inmutables. Cada evento representa un cambio único y atómico en el estado de la aplicación y se almacena de manera secuencial en un registro de eventos, generalmente llamado "log de eventos" o "journal". Este enfoque permite reconstruir el estado actual de la aplicación en cualquier punto en el tiempo mediante la reproducción de todos los eventos desde el inicio hasta el momento deseado.

Características de Event Sourcing

Ventajas de Event Sourcing

Desafíos del patrón Event Sourcing

En resumen, el patrón Event Sourcing es una técnica poderosa para gestionar el estado de una aplicación de manera eficiente y escalable. Si se implementa correctamente, puede proporcionar una mayor transparencia, flexibilidad y capacidad de evolución en comparación con otros enfoques más tradicionales. Sin embargo, también presenta desafíos en términos de complejidad de implementación y escalabilidad del registro de eventos.

Ejemplo

Esto sería un evento base que representa cualquier acción que ocurra en nuestro sistema:

import java.time.LocalDateTime;

public abstract class Event {
    private final LocalDateTime timestamp;

    public Event() {
        this.timestamp = LocalDateTime.now();
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }
}

Eventos específicos para acciones como:

public class PurchaseEvent extends Event {
    private final String purchaseId;
    // Otros campos relevantes
    
    public PurchaseEvent(String purchaseId) {
        this.purchaseId = purchaseId;
        // Inicializar otros campos
    }
    
    // Getters para campos relevantes
}
public class PaymentEvent extends Event {
    private final String paymentId;
    // Otros campos relevantes
        
    public PaymentEvent(String paymentId) {
        this.paymentId = paymentId;
        // Inicializar otros campos
    }
        
    // Getters para campos relevantes
}
public class ShippingEvent extends Event {
    private final String shipmentId;
    // Otros campos relevantes
            
    public ShippingEvent(String shipmentId) {
        this.shipmentId = shipmentId;
        // Inicializar otros campos
    }
            
    // Getters para campos relevantes
}

Luego, creamos un registro de eventos donde almacenaremos todos los eventos que ocurren en el sistema (en un caso real se deben utilizar técnicas de persistencia adecuadas para loguear los datos, colas, brokers de mensajería, etc.):

import java.util.ArrayList;
import java.util.List;

public class EventLog {
    private final List<Event> events;

    public EventLog() {
        this.events = new ArrayList<>();
    }

    public void addEvent(Event event) {
        events.add(event);
    }

    public List<Event> getEvents() {
        return events;
    }
}

Y, finalmente, en nuestro servicio de aplicación, podemos registrar eventos cada vez que ocurra una acción relevante en el sistema:

Para la realización de la compra:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PurchaseService {
    private final EventLog eventLog;

    @Autowired
    public PurchaseService(EventLog eventLog) {
        this.eventLog = eventLog;
    }

    public void makePurchase(String purchaseId) {
        // Lógica para realizar la compra
        
        // Registrar el evento de compra
        PurchaseEvent purchaseEvent = new PurchaseEvent(purchaseId);
        eventLog.addEvent(purchaseEvent);
    }
}

Para la realización del pago:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class PaymentService {
    private final EventLog eventLog;

    @Autowired
    public PaymentService(EventLog eventLog) {
        this.eventLog = eventLog;
    }

    public void processPayment(String paymentId) {
        // Lógica para procesar el pago
        
        // Registrar el evento de pago
        PaymentEvent paymentEvent = new PaymentEvent(paymentId);
        eventLog.addEvent(paymentEvent);
    }
}

Y para el envío:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class ShippingService {
    private final EventLog eventLog;

    @Autowired
    public ShippingService(EventLog eventLog) {
        this.eventLog = eventLog;
    }

    public void shipProducts(String shipmentId) {
        // Lógica para enviar los productos
        
        // Registrar el evento de envío
        ShippingEvent shippingEvent = new ShippingEvent(shipmentId);
        eventLog.addEvent(shippingEvent);
    }
}

Ya vemos que de manera muy sencilla se puede montar un sistema de event sourcing simple. Esto nos puede servir para una trazabilidad de los pedidos, pudiendo ver, por ejemplo, cuáles se han completado correctamente o en cuáles ha habido algún problema mediante un servicio de informes o dashboard.

Event sourcing

EDA

La arquitectura orientada a eventos es un estilo arquitectónico que se centra en la generación, detección, procesamiento y respuesta a eventos que ocurren dentro de un sistema o entre sistemas distribuidos. En EDA, los sistemas están diseñados para ser altamente reactivos a los eventos, lo que permite una comunicación asincrónica y desacoplada entre los componentes del sistema.

Características de la arquitectura orientada a eventos

Ventajas de la arquitectura orientada a eventos

Desafíos de la arquitectura orientada a eventos

En resumen, la arquitectura orientada a eventos es un enfoque arquitectónico poderoso que se centra en la generación, detección, procesamiento y respuesta a eventos dentro de un sistema o entre sistemas distribuidos. Ofrece numerosas ventajas, como desacoplamiento, escalabilidad y reactividad, pero también presenta desafíos en términos de diseño, gestión y monitoreo del sistema.

Ejemplo

Supongamos que tenemos un sistema donde los usuarios pueden realizar pedidos y recibir notificaciones cuando se envían los productos. Utilizaremos Kafka para enviar eventos relacionados con los pedidos y las notificaciones.

Primero, necesitamos configurar Kafka en nuestro proyecto, lo haremos utilizando Spring Boot:

import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;

@Configuration
@EnableKafka
public class KafkaConfig {
    // Configuración de Kafka
}

Ahora, definimos un productor de eventos que enviará eventos de pedido a un tema de Kafka:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

@Component
public class OrderEventProducer {

    private static final String TOPIC = "order-events";

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void sendOrderEvent(String orderId) {
        kafkaTemplate.send(TOPIC, orderId);
    }
}

A continuación, creamos un consumidor de eventos que escuchará el topic de Kafka y procesará los eventos de pedidos:

import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventListener {

    @KafkaListener(topics = "order-events", groupId = "order-group")
    public void receiveOrderEvent(String orderId) {
        // Procesar el evento de pedido
        System.out.println("Pedido recibido: " + orderId);
        // Lógica para procesar el pedido, como enviar notificaciones, actualizar el estado del pedido, etc.
    }
}

Por último, en nuestro servicio de aplicación donde se realiza un pedido, podemos enviar un evento de pedido al productor de eventos:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final OrderEventProducer orderEventProducer;

    @Autowired
    public OrderService(OrderEventProducer orderEventProducer) {
        this.orderEventProducer = orderEventProducer;
    }

    public void placeOrder(String orderId) {
        // Lógica para realizar el pedido
        
        // Enviar un evento de pedido
        orderEventProducer.sendOrderEvent(orderId);
    }
}

Vemos que cuando se realiza un pedido en el sistema, se envía un evento de pedido al topic de Kafka utilizando el productor de eventos. El consumidor de eventos escucha el topic de Kafka y procesa los eventos de pedido, como enviar notificaciones o actualizar el estado del pedido.

Este es solo un ejemplo simple para ilustrar cómo implementar un sistema basado en arquitectura orientada a eventos utilizando Java y Kafka. Dependiendo de tus requisitos específicos, puedes agregar más lógica y funcionalidades a tu sistema.

Aquí vemos un diagrama que muestra un ejemplo de una arquitectura basada en eventos (EDA) para otros casos de uso de un e-commerce:

Este ejemplo muestra tres componentes de productores de eventos y los eventos que producen. También vemos un enrutador de eventos que toma y filtra los eventos, luego envía estos eventos al consumidor de eventos pertinente.

La arquitectura basada en eventos permite que el sitio reaccione a los cambios de una variedad de fuentes durante los momentos de máxima demanda, sin bloquear la aplicación ni aprovisionar recursos en exceso.

¿Event Sourcing = EDA?

No!, Event Sourcing y EDA son conceptos relacionados, pero distintos en el ámbito de la ingeniería de software.

Event Sourcing es un patrón de diseño de software que consiste en almacenar un registro secuencial de todos los cambios que ocurren en el estado de una aplicación como una secuencia de eventos inmutables. Cada evento representa un cambio único y atómico en el estado de la aplicación y se almacena de manera secuencial en un registro de eventos. El objetivo principal del Event Sourcing es mantener un historial completo y auditado de todos los cambios en el estado de la aplicación y permitir la reconstrucción del estado actual de la aplicación en cualquier momento a partir de la reproducción de todos los eventos.

La arquitectura orientada a eventos es un enfoque arquitectónico que se centra en el intercambio de eventos entre los componentes de un sistema distribuido. En un sistema orientado a eventos, los componentes pueden emitir eventos cuando ocurren ciertas acciones o cambios de estado, y otros componentes pueden reaccionar a esos eventos y realizar acciones correspondientes. EDA se centra en la comunicación asincrónica y la independencia entre los componentes del sistema, lo que permite una mayor flexibilidad, escalabilidad y desacoplamiento.

Entre sus principales diferencias:

En resumen, aunque Event Sourcing y arquitectura orientada a eventos comparten similitudes en cuanto al uso de eventos, tienen enfoques y objetivos distintos en el diseño y la implementación de sistemas de software. ¿Tienes alguna duda o consulta? ¡Déjanos un comentario!

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.