❓ Qué problema resuelve

  • Acoplamiento: Reduce dependencias directas entre componentes
  • Escalabilidad: Permite procesamiento asíncrono y distribución de carga
  • Extensibilidad: Fácil agregar nuevas funcionalidades sin modificar código existente
  • Auditoria: Historial completo de cambios en el sistema

🔧 Cómo funciona

Los componentes se comunican a través de eventos, donde los productores publican eventos y los consumidores se suscriben a los eventos que les interesan.

Conceptos Clave:

  • Events: Representan algo que ocurrió en el pasado
  • Event Publishers: Publican eventos
  • Event Handlers: Procesan eventos
  • Event Store: Almacena eventos
  • Event Bus: Mecanismo de distribución de eventos

📊 Diagrama

graph TB
    subgraph "Event-Driven System"
        subgraph "Order Service"
            OS[Order Service]
            OEP[Order Event Publisher]
        end
        
        subgraph "Event Infrastructure"
            EB[Event Bus]
            ES[(Event Store)]
        end
        
        subgraph "Event Consumers"
            IH[Inventory Handler]
            EH[Email Handler]
            AH[Analytics Handler]
            PH[Payment Handler]
        end
        
        subgraph "External Services"
            IS[Inventory Service]
            EM[Email Service]
            AS[Analytics Service]
            PS[Payment Service]
        end
    end
    
    OS --> OEP
    OEP --> EB
    EB --> ES
    EB --> IH
    EB --> EH
    EB --> AH
    EB --> PH
    
    IH --> IS
    EH --> EM
    AH --> AS
    PH --> PS

☕ Ejemplo en Java

Event Base

public abstract class DomainEvent {
    private final String eventId;
    private final LocalDateTime occurredOn;
    private final String aggregateId;
    
    protected DomainEvent(String aggregateId) {
        this.eventId = UUID.randomUUID().toString();
        this.occurredOn = LocalDateTime.now();
        this.aggregateId = aggregateId;
    }
    
    // getters...
}

Eventos Específicos

public class OrderCreatedEvent extends DomainEvent {
    private final String customerId;
    private final BigDecimal totalAmount;
    private final List<OrderItem> items;
    
    public OrderCreatedEvent(String orderId, String customerId, 
                           BigDecimal totalAmount, List<OrderItem> items) {
        super(orderId);
        this.customerId = customerId;
        this.totalAmount = totalAmount;
        this.items = new ArrayList<>(items);
    }
    
    // getters...
}

public class PaymentProcessedEvent extends DomainEvent {
    private final String orderId;
    private final BigDecimal amount;
    private final PaymentStatus status;
    
    public PaymentProcessedEvent(String paymentId, String orderId, 
                               BigDecimal amount, PaymentStatus status) {
        super(paymentId);
        this.orderId = orderId;
        this.amount = amount;
        this.status = status;
    }
    
    // getters...
}

Event Publisher

@Component
public class EventPublisher {
    private final ApplicationEventPublisher applicationEventPublisher;
    private final EventStore eventStore;
    
    public EventPublisher(ApplicationEventPublisher applicationEventPublisher,
                         EventStore eventStore) {
        this.applicationEventPublisher = applicationEventPublisher;
        this.eventStore = eventStore;
    }
    
    public void publish(DomainEvent event) {
        // Guardar en event store
        eventStore.save(event);
        
        // Publicar para procesamiento inmediato
        applicationEventPublisher.publishEvent(event);
        
        // Opcional: Publicar a message broker externo
        // messageBroker.publish(event);
    }
    
    public void publishAll(List<DomainEvent> events) {
        events.forEach(this::publish);
    }
}

Event Handlers

@Component
public class InventoryEventHandler {
    private final InventoryService inventoryService;
    
    public InventoryEventHandler(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }
    
    @EventListener
    @Async
    public void handle(OrderCreatedEvent event) {
        try {
            inventoryService.reserveItems(event.getItems());
            log.info("Inventory reserved for order: {}", event.getAggregateId());
        } catch (Exception e) {
            log.error("Failed to reserve inventory for order: {}", 
                     event.getAggregateId(), e);
            // Publicar evento de compensación
        }
    }
}

@Component
public class EmailNotificationHandler {
    private final EmailService emailService;
    private final CustomerService customerService;
    
    public EmailNotificationHandler(EmailService emailService,
                                  CustomerService customerService) {
        this.emailService = emailService;
        this.customerService = customerService;
    }
    
    @EventListener
    @Async
    public void handle(OrderCreatedEvent event) {
        Customer customer = customerService.findById(event.getCustomerId());
        
        EmailTemplate template = EmailTemplate.builder()
            .to(customer.getEmail())
            .subject("Order Confirmation")
            .template("order-confirmation")
            .variable("orderId", event.getAggregateId())
            .variable("totalAmount", event.getTotalAmount())
            .build();
            
        emailService.send(template);
    }
    
    @EventListener
    @Async
    public void handle(PaymentProcessedEvent event) {
        if (event.getStatus() == PaymentStatus.COMPLETED) {
            // Enviar email de pago confirmado
        } else {
            // Enviar email de pago fallido
        }
    }
}

Event Store

@Entity
@Table(name = "event_store")
public class StoredEvent {
    @Id
    private String eventId;
    private String aggregateId;
    private String eventType;
    private String eventData;
    private LocalDateTime occurredOn;
    private String version;
    
    // Constructor, getters y setters...
}

@Repository
public interface EventStore extends JpaRepository<StoredEvent, String> {
    
    void save(DomainEvent event);
    
    List<StoredEvent> findByAggregateIdOrderByOccurredOn(String aggregateId);
    
    List<StoredEvent> findByEventTypeAndOccurredOnAfter(String eventType, 
                                                       LocalDateTime after);
}

@Component
public class JpaEventStore implements EventStore {
    private final StoredEventRepository repository;
    private final ObjectMapper objectMapper;
    
    public JpaEventStore(StoredEventRepository repository, ObjectMapper objectMapper) {
        this.repository = repository;
        this.objectMapper = objectMapper;
    }
    
    @Override
    public void save(DomainEvent event) {
        try {
            StoredEvent storedEvent = new StoredEvent(
                event.getEventId(),
                event.getAggregateId(),
                event.getClass().getSimpleName(),
                objectMapper.writeValueAsString(event),
                event.getOccurredOn(),
                "1.0"
            );
            
            repository.save(storedEvent);
        } catch (Exception e) {
            throw new RuntimeException("Failed to store event", e);
        }
    }
}

Aggregate con Eventos

public class Order {
    private OrderId id;
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    private List<DomainEvent> domainEvents;
    
    public Order(OrderId id, CustomerId customerId) {
        this.id = id;
        this.customerId = customerId;
        this.items = new ArrayList<>();
        this.status = OrderStatus.PENDING;
        this.domainEvents = new ArrayList<>();
    }
    
    public void addItem(Product product, int quantity) {
        OrderItem item = new OrderItem(product, quantity);
        items.add(item);
        
        // Registrar evento
        addDomainEvent(new OrderItemAddedEvent(
            id.getValue(),
            product.getId().getValue(),
            quantity
        ));
    }
    
    public void confirm() {
        if (items.isEmpty()) {
            throw new IllegalStateException("Cannot confirm empty order");
        }
        
        this.status = OrderStatus.CONFIRMED;
        
        // Registrar evento
        addDomainEvent(new OrderCreatedEvent(
            id.getValue(),
            customerId.getValue(),
            calculateTotal(),
            new ArrayList<>(items)
        ));
    }
    
    private void addDomainEvent(DomainEvent event) {
        this.domainEvents.add(event);
    }
    
    public List<DomainEvent> getDomainEvents() {
        return new ArrayList<>(domainEvents);
    }
    
    public void clearDomainEvents() {
        this.domainEvents.clear();
    }
}