Backend

99 Ngày Spring — Ngày 63: Application events

SSite Admin
28 tháng 09, 2026 2 phút đọc 0 lượt xem
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.

Application events

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

S

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.

Bài viết liên quan

99 Ngày Java — Ngày 63: volatile và Atomic

Phân biệt visibility với atomicity, happens-before cơ bản, volatile flag, AtomicInteger và CAS.

28 thg 9, 20263 phút0
99 Ngày Spring — Ngày 62: Custom annotation kết hợp AOP

Tạo @Timed bằng metadata runtime, chọn method với @annotation, đo thời gian bằng @Around và giữ nguyên kết quả, lỗi.

27 thg 9, 20262 phút6
99 Ngày Java — Ngày 62: Race condition và synchronized

Vì sao counter++ mất cập nhật, cách synchronized bảo vệ shared state, intrinsic lock và các bẫy về phạm vi khóa.

27 thg 9, 20263 phút6