Real World Example

Order Placed in a Modular Monolith

Zomato, Swiggy and many startups begin as a modular monolith: one Spring Boot app with clear modules (orders, payments, notifications, loyalty). Events keep modules independent, so a module can later move into its own microservice and the event becomes a Kafka message.

// orders module
@Transactional
public OrderResponse placeOrder(PlaceOrderRequest req) {
    Order order = orders.save(Order.from(req));
    stockService.reserve(order);                       // throws if out of stock -> rollback
    events.publishEvent(new OrderPlacedEvent(order.getId(), req.userId(), order.getTotal()));
    return OrderResponse.from(order);
}

// notifications module
@Async
@TransactionalEventListener
public void notifyCustomer(OrderPlacedEvent e) { smsClient.send(e.userId(), "Order " + e.orderId() + " placed!"); }

// loyalty module
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addPoints(OrderPlacedEvent e) { loyalty.add(e.userId(), e.total().intValue() / 100); }

Example

Success and failure cases:

curl -X POST localhost:8080/api/orders -d '{"userId":7,"items":[{"productId":3,"quantity":1}]}' -H "Content-Type: application/json"
curl -X POST localhost:8080/api/orders -d '{"userId":7,"items":[{"productId":3,"quantity":500}]}' -H "Content-Type: application/json"

Example output

HTTP/1.1 201 Created   {"orderId":1001,"status":"PLACED"}
INFO  notifications : SMS sent for order 1001
INFO  loyalty       : user 7 +549 points

HTTP/1.1 409 Conflict  {"title":"Out of stock","detail":"Only 11 units of product 3 left"}
(no SMS, no points: the transaction rolled back, so no listener ran)