Developer-kit spring-boot-event-driven-patterns
Provides Event-Driven Architecture (EDA) patterns for Spring Boot — creates domain events, configures ApplicationEvent and @TransactionalEventListener, sets up Kafka producers and consumers, and implements the transactional outbox pattern for reliable distributed messaging. Use when implementing event-driven systems in Spring Boot, setting up async messaging with Kafka, publishing domain events from DDD aggregates, or needing reliable event publishing with the outbox pattern.
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-java/skills/spring-boot-event-driven-patterns" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-spring-boot-event-driven-patterns && rm -rf "$T"
plugins/developer-kit-java/skills/spring-boot-event-driven-patterns/SKILL.mdSpring Boot Event-Driven Patterns
Overview
Implement Event-Driven Architecture (EDA) patterns in Spring Boot 3.x using domain events, ApplicationEventPublisher,
@TransactionalEventListener, and distributed messaging with Kafka and Spring Cloud Stream.
When to Use
- Implementing event-driven microservices with Kafka messaging
- Publishing domain events from aggregate roots in DDD architectures
- Setting up transactional event listeners that fire after database commits
- Adding async messaging with producers and consumers via Spring Kafka
- Ensuring reliable event delivery using the transactional outbox pattern
- Replacing synchronous calls with event-based communication between services
Quick Reference
| Concept | Description |
|---|---|
| Domain Events | Immutable events extending base class with eventId, occurredAt, correlationId |
| Event Publishing | for local, for distributed |
| Event Listening | for reliable handling |
| Kafka | for distributed event consumption |
| Spring Cloud Stream | Functional programming model with beans |
| Outbox Pattern | Atomic event storage with business data, scheduled publisher |
Examples
Monolithic to Event-Driven Refactoring
Before (Anti-Pattern):
@Transactional public Order processOrder(OrderRequest request) { Order order = orderRepository.save(request); inventoryService.reserve(order.getItems()); // Blocking paymentService.charge(order.getPayment()); // Blocking emailService.sendConfirmation(order); // Blocking return order; }
After (Event-Driven):
@Transactional public Order processOrder(OrderRequest request) { Order order = Order.create(request); orderRepository.save(order); // Publish event after transaction commits eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), order.getItems())); return order; } @Component public class OrderEventHandler { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleOrderCreated(OrderCreatedEvent event) { // Execute asynchronously after the order is saved inventoryService.reserve(event.getItems()); paymentService.charge(event.getPayment()); } }
See examples.md for complete working examples.
Instructions
1. Design Domain Events
Create immutable event classes extending a base
DomainEvent class:
public abstract class DomainEvent { private final UUID eventId; private final LocalDateTime occurredAt; private final UUID correlationId; } public class ProductCreatedEvent extends DomainEvent { private final ProductId productId; private final String name; private final BigDecimal price; }
See domain-events-design.md for patterns.
2. Publish Events from Aggregates
Add domain events to aggregate roots, publish via
ApplicationEventPublisher:
@Service @Transactional public class ProductService { public Product createProduct(CreateProductRequest request) { Product product = Product.create(request.getName(), request.getPrice(), request.getStock()); repository.save(product); product.getDomainEvents().forEach(eventPublisher::publishEvent); product.clearDomainEvents(); return product; } }
See aggregate-root-patterns.md for DDD patterns.
3. Handle Events Transactionally
Use
@TransactionalEventListener for reliable event handling:
@Component public class ProductEventHandler { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onProductCreated(ProductCreatedEvent event) { notificationService.sendProductCreatedNotification(event.getName()); } }
Validate: Confirm the event handler fires only after the transaction commits by checking that the database state is committed before the handler executes.
See event-handling.md for handling patterns.
4. Configure Kafka Infrastructure
Configure KafkaTemplate for publishing,
@KafkaListener for consuming:
spring: kafka: bootstrap-servers: localhost:9092 producer: value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
Validate: Send a test event via
KafkaTemplate and confirm it appears in the consumer logs before proceeding to production patterns.
See dependency-setup.md and configuration.md.
5. Implement Outbox Pattern
Create
OutboxEvent entity for atomic event storage:
@Entity public class OutboxEvent { private UUID id; private String aggregateId; private String eventType; private String payload; private LocalDateTime publishedAt; }
Validate: Confirm the scheduled processor picks up pending events by checking the
publishedAt timestamp is set after the scheduled run.
Scheduled processor publishes pending events. See outbox-pattern.md.
6. Handle Failure Scenarios
Implement retry logic, dead-letter queues, idempotent handlers:
@RetryableTopic(attempts = "3") @KafkaListener(topics = "product-events") public void handleProductEvent(ProductCreatedEventDto event) { orderService.onProductCreated(event); }
Validate: Confirm messages reach the dead-letter topic after exhausting retries before moving to observability.
7. Add Observability
Enable Spring Cloud Sleuth for distributed tracing, monitor metrics.
Best Practices
- Use past tense naming:
(notProductCreated
)CreateProduct - Keep events immutable: All fields should be final
- Include correlation IDs: For tracing events across services
- Use AFTER_COMMIT phase: Ensures events are published after successful database transaction
- Implement idempotent handlers: Handle duplicate events gracefully
- Add retry mechanisms: For failed event processing with exponential backoff
- Implement dead-letter queues: For events that fail processing after retries
- Log all failures: Include sufficient context for debugging
- Make handlers order-independent: Event ordering is not guaranteed in distributed systems
- Batch event processing: When handling high volumes
- Monitor event latencies: Set up alerts for slow processing
References
- dependency-setup.md — Maven/Gradle dependencies
- configuration.md — Kafka and Spring Cloud Stream configuration
- domain-events-design.md — Domain event design patterns
- aggregate-root-patterns.md — Aggregate root with event publishing
- event-publishing.md — Local and distributed event publishing
- event-handling.md — Event handling and consumption patterns
- outbox-pattern.md — Transactional outbox pattern for reliability
- testing-strategies.md — Unit and integration testing approaches
- examples.md — Complete working examples
- event-driven-patterns-reference.md — Detailed reference documentation
Constraints and Warnings
- Events published with
only fire after transaction commit@TransactionalEventListener - Avoid publishing large objects in events (memory pressure, serialization issues)
- Be cautious with async event handlers (separate threads, concurrency issues)
- Kafka consumers must handle duplicate messages (implement idempotent processing)
- Event ordering is not guaranteed in distributed systems (design handlers to be order-independent)
- Never perform blocking operations in event listeners on the main transaction thread
- Monitor for event processing backlogs (indicate system capacity issues)
Related Skills
— JWT authentication for secure event publishingspring-boot-security-jwt
— Testing event-driven applicationsspring-boot-test-patterns
— Event-driven processing with AWS Lambdaaws-sdk-java-v2-lambda
— AI-driven event processinglangchain4j-tool-function-calling-patterns