Application Events

Why Events

When an order is placed, many things should happen: send email, award loyalty points, update analytics, notify the warehouse. If OrderService calls each one directly, it depends on every other module. With events, OrderService just announces "an order was placed" and other modules listen.

OrderService --publish--> OrderPlacedEvent
                                |
          +---------------------+---------------------+
          v                     v                     v
   EmailListener        LoyaltyListener      WarehouseListener

Publishing an Event

public record OrderPlacedEvent(Long orderId, Long userId, BigDecimal total) {}   // any object can be an event

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orders;
    private final ApplicationEventPublisher events;

    @Transactional
    public OrderResponse placeOrder(PlaceOrderRequest req) {
        Order order = orders.save(Order.from(req));
        events.publishEvent(new OrderPlacedEvent(order.getId(), req.userId(), order.getTotal()));
        return OrderResponse.from(order);
    }
}

An Event Publisher in Plain Java

Example

Spring's publisher keeps a list of listeners per event type and calls each one. The core idea fits in a few lines:

import java.math.BigDecimal;
import java.util.*;
import java.util.function.*;

public class Main {
    public static void main(String[] args) {
        EventBus bus = new EventBus();
        bus.subscribe(OrderPlacedEvent.class, e -> System.out.println("Email: order " + e.orderId() + " confirmed"));
        bus.subscribe(OrderPlacedEvent.class, e -> System.out.println("Loyalty: +" + e.total().intValue() / 100 + " points for user " + e.userId()));
        bus.subscribe(OrderCancelledEvent.class, e -> System.out.println("Warehouse: stop packing order " + e.orderId()));

        bus.publish(new OrderPlacedEvent(1001L, 7L, new BigDecimal("2499.00")));
        bus.publish(new OrderCancelledEvent(1001L));
    }
}

record OrderPlacedEvent(Long orderId, Long userId, BigDecimal total) {}
record OrderCancelledEvent(Long orderId) {}

class EventBus {
    private final Map<Class<?>, List<Consumer<Object>>> listeners = new LinkedHashMap<>();

    @SuppressWarnings("unchecked")
    <T> void subscribe(Class<T> type, Consumer<T> listener) {
        listeners.computeIfAbsent(type, k -> new ArrayList<>()).add(e -> listener.accept((T) e));
    }

    void publish(Object event) {
        System.out.println("publish " + event.getClass().getSimpleName());
        listeners.getOrDefault(event.getClass(), List.of()).forEach(l -> l.accept(event));
    }
}

Output

publish OrderPlacedEvent
Email: order 1001 confirmed
Loyalty: +24 points for user 7
publish OrderCancelledEvent
Warehouse: stop packing order 1001