# Spring Boot Event-Driven Patterns - References Complete API reference for event-driven architecture in Spring Boot applications. ## Domain Event Annotations and Interfaces ### ApplicationEvent Base class for Spring events (deprecated in newer versions in favor of plain objects). ```java public abstract class ApplicationEvent extends EventObject { private final long timestamp; public ApplicationEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } public long getTimestamp() { return timestamp; } } // Modern approach: Use plain POJOs public record ProductCreatedEvent(String productId, String name, BigDecimal price) {} ``` ### Custom Domain Event Base Class ```java public abstract class DomainEvent { private final UUID eventId; private final LocalDateTime occurredAt; private final UUID correlationId; protected DomainEvent() { this.eventId = UUID.randomUUID(); this.occurredAt = LocalDateTime.now(); this.correlationId = UUID.randomUUID(); } } ``` ## Event Publishing Annotations ### @EventListener Register event listener methods. ```java @EventListener public void onProductCreated(ProductCreatedEvent event) { } @EventListener(condition = "#event.productId == '123'") // SpEL condition public void onSpecificProduct(ProductCreatedEvent event) { } @EventListener(classes = { ProductCreatedEvent.class, ProductUpdatedEvent.class }) public void onProductEvent(DomainEvent event) { } ``` ### @TransactionalEventListener Listen to events within transaction lifecycle. ```java @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onProductCreated(ProductCreatedEvent event) { } @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void beforeCommit(ProductCreatedEvent event) { } @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void afterRollback(ProductCreatedEvent event) { } ``` **TransactionPhase Values:** - `BEFORE_COMMIT` - Before transaction commits - `AFTER_COMMIT` - After successful commit (recommended) - `AFTER_ROLLBACK` - After transaction rollback - `AFTER_COMPLETION` - After transaction completion (success or rollback) ## Event Publishing Reference ### ApplicationEventPublisher Interface ```java public interface ApplicationEventPublisher { void publishEvent(ApplicationEvent event); void publishEvent(Object event); // Modern approach } ``` ### Usage Pattern ```java @Service @RequiredArgsConstructor public class ProductService { private final ApplicationEventPublisher eventPublisher; public Product create(CreateProductRequest request) { Product product = Product.create(request); Product saved = repository.save(product); // Publish events saved.getDomainEvents().forEach(eventPublisher::publishEvent); saved.clearDomainEvents(); return saved; } } ``` ## Kafka Spring Cloud Stream Reference ### Stream Binders Configuration ```yaml spring: cloud: stream: kafka: binder: brokers: localhost:9092 default-binder: kafka configuration: linger.ms: 10 batch.size: 1024 bindings: # Consumer binding productCreatedConsumer-in-0: destination: product-events group: product-service consumer: max-attempts: 3 back-off-initial-interval: 1000 back-off-max-interval: 10000 # Producer binding eventPublisher-out-0: destination: product-events producer: partition-key-expression: headers['partitionKey'] ``` ### Consumer Function Binding ```java @Configuration public class EventConsumers { @Bean public java.util.function.Consumer productCreatedConsumer( InventoryService inventoryService) { return event -> { log.info("Consumed: {}", event); inventoryService.process(event); }; } // Multiple consumers @Bean public java.util.function.Consumer productUpdatedConsumer() { return event -> { }; } } // application.yml spring.cloud.stream.bindings.productCreatedConsumer-in-0.destination=product-events spring.cloud.stream.bindings.productUpdatedConsumer-in-0.destination=product-events ``` ### Producer Function Binding ```java @Configuration public class EventProducers { @Bean public java.util.function.Supplier eventPublisher() { return () -> new ProductCreatedEvent("prod-123", "Laptop", BigDecimal.TEN); } } // application.yml spring.cloud.stream.bindings.eventPublisher-out-0.destination=product-events ``` ## Transactional Outbox Pattern Reference ### Outbox Entity Schema ```sql CREATE TABLE outbox_events ( id UUID PRIMARY KEY, aggregate_id VARCHAR(255) NOT NULL, aggregate_type VARCHAR(255), event_type VARCHAR(255) NOT NULL, payload TEXT NOT NULL, correlation_id UUID, created_at TIMESTAMP NOT NULL, published_at TIMESTAMP, retry_count INTEGER DEFAULT 0, KEY idx_published (published_at), KEY idx_created (created_at) ); ``` ### Implementation Pattern ```java // In single transaction: // 1. Update aggregate product = repository.save(product); // 2. Store events in outbox product.getDomainEvents().forEach(event -> { outboxRepository.save(new OutboxEvent( aggregateId, eventType, payload, correlationId )); }); // Then separately, scheduled task publishes from outbox @Scheduled(fixedDelay = 5000) public void publishPendingEvents() { List pending = outboxRepository.findByPublishedAtIsNull(); pending.forEach(event -> { kafkaTemplate.send(topic, event.getPayload()); event.setPublishedAt(now()); }); } ``` ## Maven Dependencies ```xml org.springframework spring-context org.springframework.kafka spring-kafka org.springframework.cloud spring-cloud-stream 4.0.4 org.springframework.cloud spring-cloud-stream-binder-kafka 4.0.4 com.fasterxml.jackson.core jackson-databind com.fasterxml.jackson.datatype jackson-datatype-jsr310 ``` ## Gradle Dependencies ```gradle dependencies { // Local Events implementation 'org.springframework:spring-context' // Kafka implementation 'org.springframework.kafka:spring-kafka' // Spring Cloud Stream implementation 'org.springframework.cloud:spring-cloud-stream:4.0.4' implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka:4.0.4' // Jackson implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' } ``` ## Event Ordering Guarantees ### Kafka Partition Key Strategy ```java // Events with same product must be in same partition kafkaTemplate.send(topic, productId, // Key: ensures ordering per product event); // Value // Consumer configuration spring.kafka.consumer.properties.isolation.level=read_committed spring.cloud.stream.kafka.binder.configuration.isolation.level=read_committed ``` ## Error Handling Patterns ### Retry with Backoff ```yaml spring: cloud: stream: bindings: eventConsumer-in-0: consumer: max-attempts: 3 back-off-initial-interval: 1000 # 1 second back-off-max-interval: 10000 # 10 seconds back-off-multiplier: 2.0 # Exponential default-retryable: true retryable-exceptions: com.example.RetryableException: true ``` ### Dead Letter Topic (DLT) ```yaml spring: cloud: stream: kafka: bindings: eventConsumer-in-0: consumer: enable-dlq: true dlq-name: product-events.dlq dlq-producer-properties: linger.ms: 5 ``` ## Idempotency Patterns ### Idempotent Consumer ```java @Component public class IdempotentEventHandler { private final IdempotencyKeyRepository idempotencyRepository; private final EventProcessingService eventService; @EventListener public void handle(DomainEvent event) throws Exception { String idempotencyKey = event.getEventId().toString(); // Check if already processed if (idempotencyRepository.exists(idempotencyKey)) { log.info("Event already processed: {}", idempotencyKey); return; } try { // Process event eventService.process(event); // Mark as processed idempotencyRepository.save(new IdempotencyKey(idempotencyKey)); } catch (Exception e) { log.error("Event processing failed: {}", idempotencyKey, e); throw e; } } } ``` ## Testing Event-Driven Systems ### Local Event Testing ```java @SpringBootTest class EventDrivenTest { @Autowired private ApplicationEventPublisher eventPublisher; @MockBean private EventHandler handler; @Test void shouldHandleEvent() { // Arrange ProductCreatedEvent event = new ProductCreatedEvent("123", "Laptop", BigDecimal.TEN); // Act eventPublisher.publishEvent(event); // Assert verify(handler).onProductCreated(event); } } ``` ### Kafka Testing with Testcontainers ```java @SpringBootTest @Testcontainers class KafkaEventTest { @Container static KafkaContainer kafka = new KafkaContainer( DockerImageName.parse("confluentinc/cp-kafka:7.5.0")); @DynamicPropertySource static void setupProperties(DynamicPropertyRegistry registry) { registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers); } @Autowired private KafkaTemplate kafkaTemplate; @Test void shouldPublishEventToKafka() throws Exception { ProductCreatedEvent event = new ProductCreatedEvent("123", "Laptop", BigDecimal.TEN); kafkaTemplate.send("product-events", "123", event).get(5, TimeUnit.SECONDS); // Verify consumption } } ``` ## Monitoring and Observability ### Spring Boot Actuator Metrics ```properties # Enable metrics management.endpoints.web.exposure.include=metrics,health # Kafka metrics kafka.controller.metrics.topic.under_replication_count kafka.log.leader_election.latency.avg ``` ### Custom Event Metrics ```java @Component @RequiredArgsConstructor public class EventMetrics { private final MeterRegistry meterRegistry; public void recordEventPublished(String eventType) { meterRegistry.counter("events.published", "type", eventType).increment(); } public void recordEventProcessed(String eventType, long durationMs) { meterRegistry.timer("events.processed", "type", eventType).record(durationMs, TimeUnit.MILLISECONDS); } public void recordEventFailed(String eventType) { meterRegistry.counter("events.failed", "type", eventType).increment(); } } ``` ## Related Skills - **spring-boot-crud-patterns** - Domain events in CRUD operations - **spring-boot-rest-api-standards** - Event notifications via webhooks - **spring-boot-test-patterns** - Testing event-driven systems - **spring-boot-dependency-injection** - Dependency injection in event handlers ## External Resources ### Official Documentation - [Spring ApplicationContext](https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html) - [Spring Cloud Stream](https://spring.io/projects/spring-cloud-stream) - [Kafka Spring Integration](https://docs.spring.io/spring-kafka/docs/current/reference/html/) ### Patterns and Best Practices - [Event Sourcing Pattern](https://martinfowler.com/eaaDev/EventSourcing.html) - [Saga Pattern](https://microservices.io/patterns/data/saga.html) - [Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html)