
【Kotlin Spring Boot 4 从零到架构师】第 49 篇模块化进阶 — 事件驱动架构本系列定位零基础入门从 Kotlin 语法一路到 Spring Boot 4 高级架构DDD Modulith适合 Java 开发者转型也适合纯新手系统学习。本篇你将学到事件驱动架构的核心概念Spring ApplicationEvent 的使用事件发布与订阅的完整实现最终一致性与事务边界Spring Modulith 事件外部化RabbitMQ学完本篇mini-shop 的模块间通信将从「直接调用」升级为「事件驱动」。一、事件驱动 vs 直接调用1.1 直接调用同步// OrderService 直接调用 NotificationServiceTransactionalfunplaceOrder(){createOrder()notificationService.sendNotification()// ← 同步调用阻塞等待inventoryService.deductStock()// ← 又一个同步调用}问题强耦合Order 依赖 Notification 和 Inventory一个失败可能影响整体无法独立扩展1.2 事件驱动异步解耦// OrderService 只管发事件TransactionalfunplaceOrder(){createOrder()eventPublisher.publish(OrderCreatedEvent(...))// ← 发完就返回}// NotificationService 监听事件EventListenerfunonOrderCreated(event:OrderCreatedEvent){sendNotification()}优势松耦合Order 不知道谁在监听可扩展新增监听者不需要改 Order可独立部署和测试下面是直接调用与事件驱动的对比流程图事件驱动发布事件异步监听异步监听OrderServiceEventBusNotificationServiceInventoryService直接调用同步调用同步调用OrderServiceNotificationServiceInventoryService二、领域事件实现2.1 定义事件packagecom.example.minishop.order.domain.eventimportjava.io.Serializableimportjava.time.LocalDateTime/** * 订单已创建事件 */dataclassOrderCreatedEvent(valorderId:Long,valuserId:Long,valtotalAmount:BigDecimal,valitems:ListOrderItemInfo,overridevaloccurredAt:LocalDateTimeLocalDateTime.now()):Serializable,DomainEvent/** * 订单已支付事件 */dataclassOrderPaidEvent(valorderId:Long,valuserId:Long,overridevaloccurredAt:LocalDateTimeLocalDateTime.now()):Serializable,DomainEvent/** * 订单已取消事件 */dataclassOrderCancelledEvent(valorderId:Long,valreason:String,overridevaloccurredAt:LocalDateTimeLocalDateTime.now()):Serializable,DomainEvent// 领域事件接口interfaceDomainEvent{valoccurredAt:LocalDateTime}dataclassOrderItemInfo(valproductId:Long,valquantity:Int,valunitPrice:BigDecimal):Serializable事件命名规范用过去式——「OrderCreated」而不是「CreateOrder」。事件描述的是「已经发生的事实」。2.2 发布事件ServiceclassOrderApplicationService(privatevalorderRepository:OrderRepository,privatevaleventPublisher:ApplicationEventPublisher){TransactionalfunplaceOrder(command:PlaceOrderCommand):Long{// 创建订单聚合根valorderOrder(userIdcommand.userId)// ... 添加商品 ...// 保存valsavedorderRepository.save(order)// 发布事件在事务提交后执行eventPublisher.publishEvent(OrderCreatedEvent(orderIdsaved.id!!,userIdsaved.userId,totalAmountsaved.totalAmount.amount,itemssaved.items.map{OrderItemInfo(it.productId,it.quantity,it.unitPrice.amount)}))returnsaved.id!!}}2.3 订阅事件// notification 模块监听订单事件packagecom.example.minishop.notificationServiceclassNotificationEventListener(privatevalnotificationService:NotificationService,privatevaluserRepository:UserRepository){privatevallogLoggerFactory.getLogger(NotificationEventListener::class.java)EventListenerfunonOrderCreated(event:OrderCreatedEvent){log.info(收到订单创建事件orderId${event.orderId})valuseruserRepository.findById(event.userId).orElse(null)?:returnnotificationService.sendEmail(touser.email,typeNotificationType.ORDER_CREATED,variablesmapOf(orderIdtoevent.orderId,totalAmounttoevent.totalAmount))}EventListenerfunonOrderPaid(event:OrderPaidEvent){log.info(收到订单支付事件orderId${event.orderId})// 发送支付成功通知}}// inventory 模块监听支付事件扣减库存ServiceclassInventoryEventListener(privatevalproductRepository:ProductRepository){EventListenerfunonOrderPaid(event:OrderPaidEvent){// 扣减实际库存log.info(订单支付完成扣减库存orderId${event.orderId})}}下面是事件发布与订阅的完整时序图InventoryEventListenerNotificationEventListenerApplicationEventPublisherOrderApplicationService客户端InventoryEventListenerNotificationEventListenerApplicationEventPublisherOrderApplicationService客户端发送邮件通知扣减库存placeOrder(command)创建订单聚合根publishEvent(OrderCreatedEvent)触发 EventListener触发 EventListener返回 orderId三、事务与事件3.1 TransactionalEventListener普通EventListener在事件发布时立刻执行可能还在原事务中。如果原事务回滚事件已经发出去了——不一致TransactionalEventListener在事务的特定阶段执行阶段时机说明BEFORE_COMMIT事务提交前事件在原事务内执行AFTER_COMMIT默认事务提交后最常用——确保数据已落库AFTER_ROLLBACK事务回滚后补偿处理AFTER_COMPLETION事务完成后提交或回滚都执行TransactionalEventListener(phaseTransactionPhase.AFTER_COMMIT)funonOrderCreated(event:OrderCreatedEvent){// 只有订单事务成功提交后才执行// 此时数据库中已经有订单记录了notificationService.sendNotification(...)}3.2 事件与事务的关系Transactional 方法开始 ├── 创建订单 ├── 保存到数据库 ├── 发布 OrderCreatedEvent ├── 方法返回 └── 事务提交 ├── 数据库变更生效 ✅ └── TransactionalEventListener(AFTER_COMMIT) 执行 ← 通知/库存等下面是TransactionalEventListener在事务不同阶段的执行流程图数据库变更生效触发 AFTER_COMMIT如果事务回滚触发 AFTER_ROLLBACKTransactional 方法开始创建订单保存到数据库发布 OrderCreatedEvent方法返回事务提交数据库 ✅TransactionalEventListener(AFTER_COMMIT)发送通知扣减库存事务回滚 ❌补偿处理四、Spring Modulith 事件外部化4.1 本地事件 → 消息队列Spring Modulith 可以自动把模块间事件发布到 RabbitMQ/Kafka实现跨进程通信dependencies{implementation(org.springframework.modulith:spring-modulith-events-api:1.4.0)implementation(org.springframework.modulith:spring-modulith-events-amqp:1.4.0)}spring:modulith:events:amqp:enabled:true# 启用 AMQP 事件外部化bindings:# 事件路由com.example.minishop.order.OrderCreatedEvent:exchange:order.events# 路由到 order.events 交换机当OrderApplicationService发布OrderCreatedEvent时Modulith 同时在本地触发EventListener同进程内发送到 RabbitMQ 的order.events交换机跨进程五、事件驱动的权衡5.1 优势优势说明解耦模块间不直接依赖可扩展新增消费者不改发布方独立部署消费者可以独立部署和扩展5.2 挑战挑战解决方案最终一致性消费者异步处理数据有短暂延迟调试困难链路追踪Trace ID事件丢失事件持久化 重试机制事件顺序用分区Partition保证顺序何时用事件驱动跨模块通信、不需要同步结果的场景。如果需要立即拿到结果如查询商品信息还是用接口查询。本篇小结知识点核心内容事件驱动模块间松耦合通信领域事件用过去式命名OrderCreatedEventApplicationEventPublisher发布事件EventListener同步监听事件TransactionalEventListener事务阶段监听AFTER_COMMIT事务提交后执行最常用事件接口查询同步读取用接口副作用用事件Modulith 事件外部化本地事件 → RabbitMQ最终一致性事件驱动的代价下篇预告第 50 篇多数据源集成主库 PostgreSQL 做业务分析库 ClickHouse 做统计——多数据源怎么配置下一篇实战多数据源集成。如果本篇内容对你有帮助欢迎点赞收藏有任何疑问欢迎在评论区交流。