spring-boot-event-driven-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpring Boot Event-Driven Patterns
Spring Boot 事件驱动模式
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.
在Spring Boot 3.x中使用领域事件、ApplicationEventPublisher、@TransactionalEventListener,以及结合Kafka和Spring Cloud Stream的分布式消息实现事件驱动架构(EDA)模式。
When to Use This Skill
适用场景
Use this skill when building applications that require:
- Loose coupling between microservices through event-based communication
- Domain event publishing from aggregate roots in DDD architectures
- Transactional event listeners ensuring consistency after database commits
- Distributed messaging with Kafka for inter-service communication
- Event streaming with Spring Cloud Stream for reactive systems
- Reliability using the transactional outbox pattern
- Asynchronous communication between bounded contexts
- Event sourcing foundations with proper event sourcing patterns
当你构建的应用需要以下特性时,可使用该技能:
- 通过基于事件的通信实现微服务间的松耦合
- 在DDD架构中从聚合根发布领域事件
- 事务事件监听器确保数据库提交后的一致性
- 使用Kafka进行服务间通信的分布式消息
- 适用于响应式系统的Spring Cloud Stream事件流
- 使用事务性发件箱模式保障可靠性
- 限界上下文之间的异步通信
- 具备完善事件溯源模式的事件溯源基础
Setup and Configuration
搭建与配置
Required Dependencies
必要依赖
To implement event-driven patterns, include these dependencies in your project:
Maven:
xml
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Kafka for distributed messaging -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Spring Cloud Stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>4.0.4</version> // Use latest compatible version
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers for integration testing -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>Gradle:
gradle
dependencies {
// Spring Boot Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Data JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Kafka
implementation 'org.springframework.kafka:spring-kafka'
// Spring Cloud Stream
implementation 'org.springframework.cloud:spring-cloud-stream:4.0.4'
// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers:1.19.0'
}要实现事件驱动模式,请在项目中添加以下依赖:
Maven:
xml
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Kafka for distributed messaging -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Spring Cloud Stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>4.0.4</version> // Use latest compatible version
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers for integration testing -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>Gradle:
gradle
dependencies {
// Spring Boot Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Data JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Kafka
implementation 'org.springframework.kafka:spring-kafka'
// Spring Cloud Stream
implementation 'org.springframework.cloud:spring-cloud-stream:4.0.4'
// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers:1.19.0'
}Basic Configuration
基础配置
Configure your application for event-driven architecture:
properties
undefined为应用配置事件驱动架构:
properties
undefinedServer Configuration
Server Configuration
server.port=8080
server.port=8080
Kafka Configuration
Kafka Configuration
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
Spring Cloud Stream Configuration
Spring Cloud Stream Configuration
spring.cloud.stream.kafka.binder.brokers=localhost:9092
undefinedspring.cloud.stream.kafka.binder.brokers=localhost:9092
undefinedCore Patterns
核心模式
1. Domain Events Design
1. 领域事件设计
Create immutable domain events for business domain changes:
java
// Domain event base class
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();
}
protected DomainEvent(UUID correlationId) {
this.eventId = UUID.randomUUID();
this.occurredAt = LocalDateTime.now();
this.correlationId = correlationId;
}
// Getters
public UUID getEventId() { return eventId; }
public LocalDateTime getOccurredAt() { return occurredAt; }
public UUID getCorrelationId() { return correlationId; }
}
// Specific domain events
public class ProductCreatedEvent extends DomainEvent {
private final ProductId productId;
private final String name;
private final BigDecimal price;
private final Integer stock;
public ProductCreatedEvent(ProductId productId, String name, BigDecimal price, Integer stock) {
super();
this.productId = productId;
this.name = name;
this.price = price;
this.stock = stock;
}
// Getters
public ProductId getProductId() { return productId; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
public Integer getStock() { return stock; }
}为业务领域变更创建不可变的领域事件:
java
// Domain event base class
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();
}
protected DomainEvent(UUID correlationId) {
this.eventId = UUID.randomUUID();
this.occurredAt = LocalDateTime.now();
this.correlationId = correlationId;
}
// Getters
public UUID getEventId() { return eventId; }
public LocalDateTime getOccurredAt() { return occurredAt; }
public UUID getCorrelationId() { return correlationId; }
}
// Specific domain events
public class ProductCreatedEvent extends DomainEvent {
private final ProductId productId;
private final String name;
private final BigDecimal price;
private final Integer stock;
public ProductCreatedEvent(ProductId productId, String name, BigDecimal price, Integer stock) {
super();
this.productId = productId;
this.name = name;
this.price = price;
this.stock = stock;
}
// Getters
public ProductId getProductId() { return productId; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
public Integer getStock() { return stock; }
}2. Aggregate Root with Event Publishing
2. 带事件发布的聚合根
Implement aggregates that publish domain events:
java
@Entity
@Getter
@ToString
@EqualsAndHashCode(of = "id")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id
private ProductId id;
private String name;
private BigDecimal price;
private Integer stock;
@Transient
private List<DomainEvent> domainEvents = new ArrayList<>();
public static Product create(String name, BigDecimal price, Integer stock) {
Product product = new Product();
product.id = ProductId.generate();
product.name = name;
product.price = price;
product.stock = stock;
product.domainEvents.add(new ProductCreatedEvent(product.id, name, price, stock));
return product;
}
public void decreaseStock(Integer quantity) {
this.stock -= quantity;
this.domainEvents.add(new ProductStockDecreasedEvent(this.id, quantity, this.stock));
}
public List<DomainEvent> getDomainEvents() {
return new ArrayList<>(domainEvents);
}
public void clearDomainEvents() {
domainEvents.clear();
}
}实现可发布领域事件的聚合根:
java
@Entity
@Getter
@ToString
@EqualsAndHashCode(of = "id")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id
private ProductId id;
private String name;
private BigDecimal price;
private Integer stock;
@Transient
private List<DomainEvent> domainEvents = new ArrayList<>();
public static Product create(String name, BigDecimal price, Integer stock) {
Product product = new Product();
product.id = ProductId.generate();
product.name = name;
product.price = price;
product.stock = stock;
product.domainEvents.add(new ProductCreatedEvent(product.id, name, price, stock));
return product;
}
public void decreaseStock(Integer quantity) {
this.stock -= quantity;
this.domainEvents.add(new ProductStockDecreasedEvent(this.id, quantity, this.stock));
}
public List<DomainEvent> getDomainEvents() {
return new ArrayList<>(domainEvents);
}
public void clearDomainEvents() {
domainEvents.clear();
}
}3. Application Event Publishing
3. 应用事件发布
Publish domain events from application services:
java
@Service
@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(),
request.getStock()
);
productRepository.save(product);
// Publish domain events
product.getDomainEvents().forEach(eventPublisher::publishEvent);
product.clearDomainEvents();
return mapToResponse(product);
}
}从应用服务发布领域事件:
java
@Service
@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(),
request.getStock()
);
productRepository.save(product);
// Publish domain events
product.getDomainEvents().forEach(eventPublisher::publishEvent);
product.clearDomainEvents();
return mapToResponse(product);
}
}4. Local Event Handling
4. 本地事件处理
Handle events with transactional event listeners:
java
@Component
@RequiredArgsConstructor
public class ProductEventHandler {
private final NotificationService notificationService;
private final AuditService auditService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProductCreated(ProductCreatedEvent event) {
auditService.logProductCreation(
event.getProductId().getValue(),
event.getName(),
event.getPrice(),
event.getCorrelationId()
);
notificationService.sendProductCreatedNotification(event.getName());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProductStockDecreased(ProductStockDecreasedEvent event) {
notificationService.sendStockUpdateNotification(
event.getProductId().getValue(),
event.getQuantity()
);
}
}使用事务事件监听器处理事件:
java
@Component
@RequiredArgsConstructor
public class ProductEventHandler {
private final NotificationService notificationService;
private final AuditService auditService;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProductCreated(ProductCreatedEvent event) {
auditService.logProductCreation(
event.getProductId().getValue(),
event.getName(),
event.getPrice(),
event.getCorrelationId()
);
notificationService.sendProductCreatedNotification(event.getName());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProductStockDecreased(ProductStockDecreasedEvent event) {
notificationService.sendStockUpdateNotification(
event.getProductId().getValue(),
event.getQuantity()
);
}
}5. Distributed Event Publishing
5. 分布式事件发布
Publish events to Kafka for inter-service communication:
java
@Component
@RequiredArgsConstructor
public class ProductEventPublisher {
private final KafkaTemplate<String, Object> kafkaTemplate;
public void publishProductCreatedEvent(ProductCreatedEvent event) {
ProductCreatedEventDto dto = mapToDto(event);
kafkaTemplate.send("product-events", event.getProductId().getValue(), dto);
}
private ProductCreatedEventDto mapToDto(ProductCreatedEvent event) {
return new ProductCreatedEventDto(
event.getEventId(),
event.getProductId().getValue(),
event.getName(),
event.getPrice(),
event.getStock(),
event.getOccurredAt(),
event.getCorrelationId()
);
}
}将事件发布到Kafka以实现服务间通信:
java
@Component
@RequiredArgsConstructor
public class ProductEventPublisher {
private final KafkaTemplate<String, Object> kafkaTemplate;
public void publishProductCreatedEvent(ProductCreatedEvent event) {
ProductCreatedEventDto dto = mapToDto(event);
kafkaTemplate.send("product-events", event.getProductId().getValue(), dto);
}
private ProductCreatedEventDto mapToDto(ProductCreatedEvent event) {
return new ProductCreatedEventDto(
event.getEventId(),
event.getProductId().getValue(),
event.getName(),
event.getPrice(),
event.getStock(),
event.getOccurredAt(),
event.getCorrelationId()
);
}
}6. Event Consumer with Spring Cloud Stream
6. 基于Spring Cloud Stream的事件消费
Consume events using functional programming style:
java
@Component
@RequiredArgsConstructor
public class ProductEventStreamConsumer {
private final OrderService orderService;
@Bean
public Consumer<ProductCreatedEventDto> productCreatedConsumer() {
return event -> {
orderService.onProductCreated(event);
};
}
@Bean
public Consumer<ProductStockDecreasedEventDto> productStockDecreasedConsumer() {
return event -> {
orderService.onProductStockDecreased(event);
};
}
}使用函数式编程风格消费事件:
java
@Component
@RequiredArgsConstructor
public class ProductEventStreamConsumer {
private final OrderService orderService;
@Bean
public Consumer<ProductCreatedEventDto> productCreatedConsumer() {
return event -> {
orderService.onProductCreated(event);
};
}
@Bean
public Consumer<ProductStockDecreasedEventDto> productStockDecreasedConsumer() {
return event -> {
orderService.onProductStockDecreased(event);
};
}
}Advanced Patterns
进阶模式
Transactional Outbox Pattern
事务性发件箱模式
Ensure reliable event publishing with the outbox pattern:
java
@Entity
@Table(name = "outbox_events")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OutboxEvent {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String aggregateId;
private String eventType;
private String payload;
private UUID correlationId;
private LocalDateTime createdAt;
private LocalDateTime publishedAt;
private Integer retryCount;
}
@Component
@RequiredArgsConstructor
public class OutboxEventProcessor {
private final OutboxEventRepository outboxRepository;
private final KafkaTemplate<String, Object> kafkaTemplate;
@Scheduled(fixedDelay = 5000)
@Transactional
public void processPendingEvents() {
List<OutboxEvent> pendingEvents = outboxRepository.findByPublishedAtNull();
for (OutboxEvent event : pendingEvents) {
try {
kafkaTemplate.send("product-events", event.getAggregateId(), event.getPayload());
event.setPublishedAt(LocalDateTime.now());
outboxRepository.save(event);
} catch (Exception e) {
event.setRetryCount(event.getRetryCount() + 1);
outboxRepository.save(event);
}
}
}
}使用发件箱模式确保可靠的事件发布:
java
@Entity
@Table(name = "outbox_events")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OutboxEvent {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String aggregateId;
private String eventType;
private String payload;
private UUID correlationId;
private LocalDateTime createdAt;
private LocalDateTime publishedAt;
private Integer retryCount;
}
@Component
@RequiredArgsConstructor
public class OutboxEventProcessor {
private final OutboxEventRepository outboxRepository;
private final KafkaTemplate<String, Object> kafkaTemplate;
@Scheduled(fixedDelay = 5000)
@Transactional
public void processPendingEvents() {
List<OutboxEvent> pendingEvents = outboxRepository.findByPublishedAtNull();
for (OutboxEvent event : pendingEvents) {
try {
kafkaTemplate.send("product-events", event.getAggregateId(), event.getPayload());
event.setPublishedAt(LocalDateTime.now());
outboxRepository.save(event);
} catch (Exception e) {
event.setRetryCount(event.getRetryCount() + 1);
outboxRepository.save(event);
}
}
}
}Testing Strategies
测试策略
Unit Testing Domain Events
领域事件单元测试
Test domain event publishing and handling:
java
class ProductTest {
@Test
void shouldPublishProductCreatedEventOnCreation() {
Product product = Product.create("Test Product", BigDecimal.TEN, 100);
assertThat(product.getDomainEvents()).hasSize(1);
assertThat(product.getDomainEvents().get(0))
.isInstanceOf(ProductCreatedEvent.class);
}
}
@ExtendWith(MockitoExtension.class)
class ProductEventHandlerTest {
@Mock
private NotificationService notificationService;
@InjectMocks
private ProductEventHandler handler;
@Test
void shouldHandleProductCreatedEvent() {
ProductCreatedEvent event = new ProductCreatedEvent(
ProductId.of("123"), "Product", BigDecimal.TEN, 100
);
handler.onProductCreated(event);
verify(notificationService).sendProductCreatedNotification("Product");
}
}测试领域事件的发布与处理:
java
class ProductTest {
@Test
void shouldPublishProductCreatedEventOnCreation() {
Product product = Product.create("Test Product", BigDecimal.TEN, 100);
assertThat(product.getDomainEvents()).hasSize(1);
assertThat(product.getDomainEvents().get(0))
.isInstanceOf(ProductCreatedEvent.class);
}
}
@ExtendWith(MockitoExtension.class)
class ProductEventHandlerTest {
@Mock
private NotificationService notificationService;
@InjectMocks
private ProductEventHandler handler;
@Test
void shouldHandleProductCreatedEvent() {
ProductCreatedEvent event = new ProductCreatedEvent(
ProductId.of("123"), "Product", BigDecimal.TEN, 100
);
handler.onProductCreated(event);
verify(notificationService).sendProductCreatedNotification("Product");
}
}Integration Testing with Testcontainers
基于Testcontainers的集成测试
Test Kafka integration with Testcontainers:
java
@SpringBootTest
@Testcontainers
class KafkaEventIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));
@Autowired
private ProductApplicationService productService;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Test
void shouldPublishEventToKafka() {
CreateProductRequest request = new CreateProductRequest(
"Test Product", BigDecimal.valueOf(99.99), 50
);
ProductResponse response = productService.createProduct(request);
// Verify event was published
verify(eventPublisher).publishProductCreatedEvent(any(ProductCreatedEvent.class));
}
}使用Testcontainers测试Kafka集成:
java
@SpringBootTest
@Testcontainers
class KafkaEventIntegrationTest {
@Container
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));
@Autowired
private ProductApplicationService productService;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Test
void shouldPublishEventToKafka() {
CreateProductRequest request = new CreateProductRequest(
"Test Product", BigDecimal.valueOf(99.99), 50
);
ProductResponse response = productService.createProduct(request);
// Verify event was published
verify(eventPublisher).publishProductCreatedEvent(any(ProductCreatedEvent.class));
}
}Best Practices
最佳实践
Event Design Guidelines
事件设计准则
- Use past tense naming: ProductCreated, not CreateProduct
- Keep events immutable: All fields should be final
- Include correlation IDs: For tracing events across services
- Serialize to JSON: For cross-service compatibility
- 使用过去式命名:ProductCreated,而非CreateProduct
- 保持事件不可变:所有字段应为final
- 包含关联ID:用于跨服务追踪事件
- 序列化为JSON:确保跨服务兼容性
Transactional Consistency
事务一致性
- 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
- 使用AFTER_COMMIT阶段:确保事件在数据库事务成功提交后发布
- 实现幂等处理器:优雅处理重复事件
- 添加重试机制:针对失败的事件处理
Error Handling
错误处理
- Implement dead-letter queues: For events that fail processing
- Log all failures: Include sufficient context for debugging
- Set appropriate timeouts: For event processing operations
- 实现死信队列:处理处理失败的事件
- 记录所有失败:包含足够的调试上下文
- 设置合理超时:针对事件处理操作
Performance Considerations
性能考量
- Batch event processing: When handling high volumes
- Use proper partitioning: For Kafka topics
- Monitor event latencies: Set up alerts for slow processing
- 批量事件处理:处理高并发场景时使用
- 使用合适的分区:针对Kafka主题
- 监控事件延迟:为缓慢的处理设置告警
Examples and References
示例与参考
See the following resources for comprehensive examples:
- Complete working examples
- Detailed implementation patterns
查看以下资源获取完整示例:
- 完整可运行示例
- 详细实现模式
Troubleshooting
故障排除
Common Issues
常见问题
Events not being published:
- Check transaction phase configuration
- Verify ApplicationEventPublisher is properly autowired
- Ensure transaction is committed before event publishing
Kafka connection issues:
- Verify bootstrap servers configuration
- Check network connectivity to Kafka
- Ensure proper serialization configuration
Event handling failures:
- Check for circular dependencies in event handlers
- Verify transaction boundaries
- Monitor for exceptions in event processing
事件未发布:
- 检查事务阶段配置
- 验证ApplicationEventPublisher是否正确自动装配
- 确保事件发布前事务已提交
Kafka连接问题:
- 验证引导服务器配置
- 检查与Kafka的网络连通性
- 确保序列化配置正确
事件处理失败:
- 检查事件处理器中的循环依赖
- 验证事务边界
- 监控事件处理中的异常
Debug Tips
调试技巧
- Enable debug logging for Spring events:
logging.level.org.springframework.context=DEBUG - Use correlation IDs to trace events across services
- Monitor event processing metrics in Actuator endpoints
This skill provides the essential patterns and best practices for implementing event-driven architectures in Spring Boot applications.
- 启用Spring事件的调试日志:
logging.level.org.springframework.context=DEBUG - 使用关联ID跨服务追踪事件
- 在Actuator端点监控事件处理指标
本技能提供了在Spring Boot应用中实现事件驱动架构的核心模式与最佳实践。