$npx -y skills add giuseppe-trisciuoglio/developer-kit --skill spring-boot-event-driven-patternsProvides 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 w
| 1 | # Spring Boot Event-Driven Patterns |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Implementing event-driven microservices with Kafka messaging |
| 10 | - Publishing domain events from aggregate roots in DDD architectures |
| 11 | - Setting up transactional event listeners that fire after database commits |
| 12 | - Adding async messaging with producers and consumers via Spring Kafka |
| 13 | - Ensuring reliable event delivery using the transactional outbox pattern |
| 14 | - Replacing synchronous calls with event-based communication between services |
| 15 | |
| 16 | ## Quick Reference |
| 17 | |
| 18 | | Concept | Description | |
| 19 | |---------|-------------| |
| 20 | | **Domain Events** | Immutable events extending `DomainEvent` base class with eventId, occurredAt, correlationId | |
| 21 | | **Event Publishing** | `ApplicationEventPublisher.publishEvent()` for local, `KafkaTemplate` for distributed | |
| 22 | | **Event Listening** | `@TransactionalEventListener(phase = AFTER_COMMIT)` for reliable handling | |
| 23 | | **Kafka** | `@KafkaListener(topics = "...")` for distributed event consumption | |
| 24 | | **Spring Cloud Stream** | Functional programming model with `Consumer` beans | |
| 25 | | **Outbox Pattern** | Atomic event storage with business data, scheduled publisher | |
| 26 | |
| 27 | ## Examples |
| 28 | |
| 29 | ### Monolithic to Event-Driven Refactoring |
| 30 | |
| 31 | **Before (Anti-Pattern):** |
| 32 | ```java |
| 33 | @Transactional |
| 34 | public Order processOrder(OrderRequest request) { |
| 35 | Order order = orderRepository.save(request); |
| 36 | inventoryService.reserve(order.getItems()); // Blocking |
| 37 | paymentService.charge(order.getPayment()); // Blocking |
| 38 | emailService.sendConfirmation(order); // Blocking |
| 39 | return order; |
| 40 | } |
| 41 | ``` |
| 42 | |
| 43 | **After (Event-Driven):** |
| 44 | ```java |
| 45 | @Transactional |
| 46 | public Order processOrder(OrderRequest request) { |
| 47 | Order order = Order.create(request); |
| 48 | orderRepository.save(order); |
| 49 | |
| 50 | // Publish event after transaction commits |
| 51 | eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), order.getItems())); |
| 52 | |
| 53 | return order; |
| 54 | } |
| 55 | |
| 56 | @Component |
| 57 | public class OrderEventHandler { |
| 58 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) |
| 59 | public void handleOrderCreated(OrderCreatedEvent event) { |
| 60 | // Execute asynchronously after the order is saved |
| 61 | inventoryService.reserve(event.getItems()); |
| 62 | paymentService.charge(event.getPayment()); |
| 63 | } |
| 64 | } |
| 65 | ``` |
| 66 | |
| 67 | See [examples.md](references/examples.md) for complete working examples. |
| 68 | |
| 69 | ## Instructions |
| 70 | |
| 71 | ### 1. Design Domain Events |
| 72 | |
| 73 | Create immutable event classes extending a base `DomainEvent` class: |
| 74 | |
| 75 | ```java |
| 76 | public abstract class DomainEvent { |
| 77 | private final UUID eventId; |
| 78 | private final LocalDateTime occurredAt; |
| 79 | private final UUID correlationId; |
| 80 | } |
| 81 | |
| 82 | public class ProductCreatedEvent extends DomainEvent { |
| 83 | private final ProductId productId; |
| 84 | private final String name; |
| 85 | private final BigDecimal price; |
| 86 | } |
| 87 | ``` |
| 88 | |
| 89 | See [domain-events-design.md](references/domain-events-design.md) for patterns. |
| 90 | |
| 91 | ### 2. Publish Events from Aggregates |
| 92 | |
| 93 | Add domain events to aggregate roots, publish via `ApplicationEventPublisher`: |
| 94 | |
| 95 | ```java |
| 96 | @Service |
| 97 | @Transactional |
| 98 | public class ProductService { |
| 99 | public Product createProduct(CreateProductRequest request) { |
| 100 | Product product = Product.create(request.getName(), request.getPrice(), request.getStock()); |
| 101 | repository.save(product); |
| 102 | |
| 103 | product.getDomainEvents().forEach(eventPublisher::publishEvent); |
| 104 | product.clearDomainEvents(); |
| 105 | |
| 106 | return product; |
| 107 | } |
| 108 | } |
| 109 | ``` |
| 110 | |
| 111 | See [aggregate-root-patterns.md](references/aggregate-root-patterns.md) for DDD patterns. |
| 112 | |
| 113 | ### 3. Handle Events Transactionally |
| 114 | |
| 115 | Use `@TransactionalEventListener` for reliable event handling: |
| 116 | |
| 117 | ```java |
| 118 | @Component |
| 119 | public class ProductEventHandler { |
| 120 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) |
| 121 | public void onProductCreated(ProductCreatedEvent event) { |
| 122 | notificationService.sendProductCreatedNotification(event.getName()); |
| 123 | } |
| 124 | } |
| 125 | ``` |
| 126 | |
| 127 | **Validate:** Confirm the event handler fires only after the transaction commits by checking that the database state is committed before the handler executes. |
| 128 | |
| 129 | See [event-handling.md](references/event-handling.md) for handling patterns. |
| 130 | |
| 131 | ### 4. Configure Kafka Infrastructure |
| 132 | |
| 133 | Configure KafkaTemplate for publishing, `@KafkaListener` for consuming: |
| 134 | |
| 135 | ```yaml |
| 136 | spring: |