CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

examples.mdplugins/developer-kit-java/skills/spring-boot-event-driven-patterns/references/

Spring Boot Event-Driven Patterns - Examples

Comprehensive examples demonstrating event-driven architecture from basic local events to advanced distributed messaging.

Example 1: Basic Domain Events

A simple product lifecycle with domain events.

// Domain event
public class ProductCreatedEvent extends DomainEvent {
    private final String productId;
    private final String name;
    private final BigDecimal price;

    public ProductCreatedEvent(String productId, String name, BigDecimal price) {
        super();
        this.productId = productId;
        this.name = name;
        this.price = price;
    }

    // Getters
}

// Aggregate publishing events
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
    private String id;
    private String name;
    private BigDecimal price;
    
    @Transient
    private List<DomainEvent> domainEvents = new ArrayList<>();

    public static Product create(String name, BigDecimal price) {
        Product product = new Product();
        product.id = UUID.randomUUID().toString();
        product.name = name;
        product.price = price;
        
        // Publish domain event
        product.publishEvent(new ProductCreatedEvent(product.id, name, price));
        
        return product;
    }

    protected void publishEvent(DomainEvent event) {
        domainEvents.add(event);
    }

    public List<DomainEvent> getDomainEvents() {
        return new ArrayList<>(domainEvents);
    }

    public void clearDomainEvents() {
        domainEvents.clear();
    }
}

Example 2: Local Event Publishing

Using ApplicationEventPublisher for in-process events.

// Application service
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class ProductApplicationService {
    private final ProductRepository productRepository;
    private final ApplicationEventPublisher eventPublisher;

    public ProductResponse createProduct(CreateProductRequest request) {
        Product product = Product.create(request.getName(), request.getPrice());
        Product saved = productRepository.save(product);
        
        // Publish domain events
        saved.getDomainEvents().forEach(event -> {
            log.debug("Publishing event: {}", event.getClass().getSimpleName());
            eventPublisher.publishEvent(event);
        });
        saved.clearDomainEvents();
        
        return mapper.toResponse(saved);
    }
}

// Event listener
@Component
@Slf4j
@RequiredArgsConstructor
public class ProductEventHandler {
    private final NotificationService notificationService;
    private final InventoryService inventoryService;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onProductCreated(ProductCreatedEvent event) {
        log.info("Handling ProductCreatedEvent");
        
        // Send notification
        notificationService.sendProductCreatedNotification(
            event.getName(), event.getPrice()
        );
        
        // Update inventory
        inventoryService.registerProduct(event.getProductId());
    }
}

// Test
@SpringBootTest
class ProductEventTest {
    @Autowired
    private ProductApplicationService productService;
    
    @MockBean
    private NotificationService notificationService;
    
    @Autowired
    private ProductRepository productRepository;

    @Test
    void shouldPublishProductCreatedEvent() {
        // Act
        productService.createProduct(
            new CreateProductRequest("Laptop", BigDecimal.valueOf(999.99))
        );

        // Assert - Event was handled
        verify(notificationService).sendProductCreatedNotification(
            "Laptop", BigDecimal.valueOf(999.99)
        );
    }
}

Example 3: Transactional Outbox Pattern

Ensures reliable event publishing even on failures.

// Outbox entity
@Entity
@Table(name = "outbox_events")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OutboxEvent {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    private String aggregateId;
    private String eventType;
    
    @Column(columnDefinition = "TEXT")
    private String payload;
    
    private LocalDateTime createdAt;
    private LocalDateTime publishedAt;
    private Integer retryCount;
}

// Application service using outbox
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class ProductApplicationService {
    private final ProductRepository productRepository;
    private final OutboxEventRepository outboxRepository;
    private final ObjectMapper objectMapper;

    public ProductResponse createProduct(CreateProductRequest request) {
        Product product = Product.create(request.getName(), request.getPrice());
        Product saved = productRepository.save(product);
        
        // Store event in outbox (same transaction)
        saved.getDomainEvents().forEach(event -> {
            try {
                String payload = objectMapper.writeValueAsString(event);
                OutboxEvent outboxEvent = OutboxEvent.builder()
                    .aggregateId(saved.getId())
                    .eventType(event.getClass().getSimpleName())
                    .payload(payload)
                    .createdAt(LocalDateTime.now())
                    .retryCount(0)
                    .build();
                
                outboxRepository.save(outboxEvent);
                log.debug("Outbox event created: {}", event.getClass().getSimpleName());
            } catch (Exception e) {
                log.error("Failed to create outbox event", e);
                throw new RuntimeException(e);
            }
        });
        
        return mapper.toResponse(saved);
    }
}

// Scheduled publisher
@Component
@Slf4j
@RequiredArgsConstructor
public class OutboxEventPublisher {
    private final OutboxEventRepository outboxRepository;
    private final KafkaTemplate<String, String> kafkaTemplate;
    private final ObjectMapper objectMapper;

    @Scheduled(fixedDelay = 5000)
    @Transactional
    public void publishPendingEvents() {
        List<OutboxEvent> pending = outboxRepository.findByPublishedAtIsNull();
        
        for (OutboxEvent event : pending) {
            try {
                kafkaTemplate.send("product-events", 
                    event.getAggregateId(), event.getPayload());
                
                event.setPublishedAt(LocalDateTime.now());
                outboxRepository.save(event);
                
                log.info("Published outbox event: {}", event.getId());
            } catch (Exception e) {
                log.error("Failed to publish event: {}", event.getId(), e);
                event.setRetryCount(event.getRetryCount() + 1);
                outboxRepository.save(event);
            }
        }
    }
}

Example 4: Kafka Event Publishing

Distributed event publishing with Spring Cloud Stream.

// Application configuration
@Configuration
public class KafkaConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
}

// Event publisher
@Component
@Slf4j
@RequiredArgsConstructor
public class KafkaProductEventPublisher {
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public void publishProductCreatedEvent(ProductCreatedEvent event) {
        log.info("Publishing ProductCreatedEvent to Kafka: {}", event.getProductId());
        
        kafkaTemplate.send("product-events", 
            event.getProductId(),
            event);
    }
}

// Event consumer
@Component
@Slf4j
@RequiredArgsConstructor
public class ProductEventStreamConsumer {
    private final InventoryService inventoryService;

    @Bean
    public java.util.function.Consumer<ProductCreatedEvent> productCreatedConsumer() {
        return event -> {
            log.info("Consumed ProductCreatedEvent: {}", event.getProductId());
            inventoryService.registerProduct(event.getProductId(), event.getName());
        };
    }

    @Bean
    public java.util.function.Consumer<ProductUpdatedEvent> productUpdatedConsumer() {
        return event -> {
            log.info("Consumed ProductUpdatedEvent: {}", event.getProductId());
            inventoryService.updateProduct(event.getProductId(), event.getPrice());
        };
    }
}

// Application properties

application.yml:

spring:
  kafka:
    bootstrap-servers: localhost:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
    consumer:
      group-id: product-service
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      properties:
        spring.json.trusted.packages: "*"

  cloud:
    stream:
      bindings:
        productCreatedConsumer-in-0:
          destination: product-events
          group: product-inventory-service
        productUpdatedConsumer-in-0:
          destination: product-events
          group: product-inventory-service

Example 5: Event Saga Pattern

Coordinating multiple services with events.

// Events
public class OrderPlacedEvent extends DomainEvent {
    private final String orderId;
    private final String productId;
    private final Integer quantity;
    // ...
}

public class OrderPaymentConfirmedEvent extends DomainEvent {
    private final String orderId;
    // ...
}

// Saga orchestrator
@Component
@Slf4j
@RequiredArgsConstructor
public class OrderFulfillmentSaga {
    private final OrderService orderService;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final ApplicationEventPublisher eventPublisher;

    @Transactional
    @EventListener
    public void onOrderPlaced(OrderPlacedEvent event) {
        log.info("Starting order fulfillment saga for order: {}", event.getOrderId());
        
        try {
            // Step 1: Reserve inventory
            inventoryService.reserveStock(event.getProductId(), event.getQuantity());
            log.info("Inventory reserved for order: {}", event.getOrderId());
            
            // Step 2: Process payment
            paymentService.processPayment(event.getOrderId());
            log.info("Payment processed for order: {}", event.getOrderId());
            
            // Step 3: Publish confirmation
            eventPublisher.publishEvent(new OrderPaymentConfirmedEvent(event.getOrderId()));
            
            // Step 4: Update order status
            orderService.markAsConfirmed(event.getOrderId());
            log.info("Order confirmed: {}", event.getOrderId());
            
        } catch (PaymentFailedException e) {
            log.warn("Payment failed, releasing inventory");
            inventoryService.releaseStock(event.getProductId(), event.getQuantity());
            orderService.markAsFailed(event.getOrderId(), e.getMessage());
        }
    }
}

// Test
@SpringBootTest
class OrderFulfillmentSagaTest {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @MockBean
    private InventoryService inventoryService;
    
    @MockBean
    private PaymentService paymentService;
    
    @MockBean
    private OrderService orderService;

    @Test
    void shouldCompleteOrderFulfillmentSaga() {
        // Arrange
        OrderPlacedEvent event = new OrderPlacedEvent("order-123", "product-456", 2);

        // Act
        eventPublisher.publishEvent(event);

        // Assert
        verify(inventoryService).reserveStock("product-456", 2);
        verify(paymentService).processPayment("order-123");
        verify(orderService).markAsConfirmed("order-123");
    }
}

Example 6: Event Sourcing Foundation

Storing state changes as events.

// Event store
@Repository
public interface EventStoreRepository extends JpaRepository<StoredEvent, UUID> {
    List<StoredEvent> findByAggregateIdOrderBySequenceAsc(String aggregateId);
}

// Stored event
@Entity
@Table(name = "event_store")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoredEvent {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    private String aggregateId;
    private String eventType;
    private Integer sequence;
    
    @Column(columnDefinition = "TEXT")
    private String payload;
    
    private LocalDateTime occurredAt;
}

// Event sourcing service
@Service
@Slf4j
@RequiredArgsConstructor
public class EventSourcingService {
    private final EventStoreRepository eventStoreRepository;
    private final ObjectMapper objectMapper;

    @Transactional
    public void storeEvent(String aggregateId, DomainEvent event) {
        try {
            List<StoredEvent> existing = eventStoreRepository
                .findByAggregateIdOrderBySequenceAsc(aggregateId);
            
            Integer nextSequence = existing.isEmpty() ? 1 : 
                existing.get(existing.size() - 1).getSequence() + 1;
            
            StoredEvent storedEvent = StoredEvent.builder()
                .aggregateId(aggregateId)
                .eventType(event.getClass().getSimpleName())
                .sequence(nextSequence)
                .payload(objectMapper.writeValueAsString(event))
                .occurredAt(LocalDateTime.now())
                .build();
            
            eventStoreRepository.save(storedEvent);
            log.info("Event stored: {} for aggregate: {}", 
                event.getClass().getSimpleName(), aggregateId);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to store event", e);
        }
    }

    public List<DomainEvent> getEventHistory(String aggregateId) {
        return eventStoreRepository
            .findByAggregateIdOrderBySequenceAsc(aggregateId)
            .stream()
            .map(this::deserializeEvent)
            .collect(Collectors.toList());
    }

    private DomainEvent deserializeEvent(StoredEvent stored) {
        try {
            Class<?> eventClass = Class.forName(
                "com.example.product.domain.event." + stored.getEventType());
            return (DomainEvent) objectMapper.readValue(stored.getPayload(), eventClass);
        } catch (Exception e) {
            throw new RuntimeException("Failed to deserialize event", e);
        }
    }
}

These examples cover local events, transactional outbox pattern, Kafka publishing, saga coordination, and event sourcing foundations for comprehensive event-driven architecture.

plugins

developer-kit-java

skills

spring-boot-event-driven-patterns

README.md

tile.json