99 Ngày Spring — Ngày 63: Application events
Một đơn hàng được tạo có thể kéo theo email và cập nhật chỉ số. Gọi tất cả trong OrderService làm luồng nghiệp vụ khó đổi. Application event cho phép service công bố điều đã xảy ra, rồi các listener phản ứng. Đây là cơ chế trong cùng ứng dụng Spring, không phải message broker hay outbox bền vững.

1. Công bố một sự kiện miền
Sự kiện có thể là một record đơn giản; không cần kế thừa ApplicationEvent. Đặt dữ liệu tối thiểu như orderId, tránh chuyển entity JPA còn lazy loading sang nơi xử lý khác. ApplicationEventPublisher gửi đến các listener trong application context hiện tại.
public record OrderPlaced(long orderId) {}
@Service
class OrderService {
private final ApplicationEventPublisher events;
OrderService(ApplicationEventPublisher events) {
this.events = events;
}
@Transactional
public void place(long id) {
// save order first
events.publishEvent(new OrderPlaced(id));
}
}2. @EventListener mặc định chạy cùng lời gọi
Mặc định listener được gọi đồng bộ bởi event multicaster. Exception từ listener có thể truyền về publisher; nếu đang trong transaction, nó có thể làm giao dịch rollback. Vì vậy đừng nghĩ publishEvent là gửi vào hàng đợi nền. Nếu listener chỉ cập nhật logic cần cùng transaction, hành vi này có thể hữu ích.
@Component
class AuditListener {
@EventListener
public void on(OrderPlaced event) {
System.out.println("placed " + event.orderId());
}
}3. Chờ commit với @TransactionalEventListener
Nếu chỉ gửi email khi đơn hàng thật sự được commit, dùng @TransactionalEventListener, phase mặc định là AFTER_COMMIT. Nếu publish ngoài transaction, listener mặc định không chạy; fallbackExecution=true chỉ dùng khi bạn muốn xử lý cả trường hợp đó. Trong callback AFTER_COMMIT, muốn ghi DB chắc chắn vào giao dịch mới thì tách sang service @Transactional(REQUIRES_NEW) hoặc dùng outbox.
@Component
class MailListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void on(OrderPlaced event) {
// Call a mail service; handle failures and retries deliberately.
}
}4. Giới hạn và kiểm thử
Event nội bộ mất khi process chết. AFTER_COMMIT không bảo đảm email được gửi nếu ứng dụng dừng ngay sau commit; outbox + worker hoặc broker giải quyết yêu cầu cần retry bền vững. Test cần kiểm tra cả nhánh commit và rollback, không chỉ assert publishEvent đã được gọi. Với listener async, lỗi và thứ tự sẽ khác nên phải thiết kế riêng.
Bài tập: một transaction publish rồi ném lỗi; listener AFTER_COMMIT có chạy không?
Bài tập: publish ngoài transaction và thử fallbackExecution.
Dùng application event để tách phản ứng trong một process, chọn phase theo hợp đồng nghiệp vụ và thêm outbox khi cần độ bền. Ngày 64 chuyển sang tác vụ chạy theo lịch.
Tài liệu đối chiếu
https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events
https://docs.spring.io/spring-framework/reference/data-access/transaction/event.html
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


