# 스프링의 @EventListener ## 왜 쓸까 - 강한 의존성을 가진 로직들을 분리해서 코드 응집도는 높이고 결합도는 낮출 수 있다. - 아래 예시 코드인 OrderFacade 는 여러 서비스에 의존하고 있어 변경에 취약하다. - 예를 들어 DataPlatform에 메시지를 전송하는 부분이 바뀌는 로직 변경이나, 새로운 작업이 추가된다면 OrderFacade를 직접 수정해야 하고 OrderFacade의 복잡도는 계속 증가할 것이다. - 또한 특정 서비스가 실패하면 전체 로직에 영향을 미치는데 특히 주문 정보 전송이 실패하더라도 일반적으로 주문은 성공해야하는데 주문 정보 전송에 의해 주문이 실패하게 되는 상황이 발생할 수도 있다. - 이는 단일책임원칙(SRP)에 위배되고 유지보수성과 확장성을 저해한다. > 동작하는 전체 코드가 아닌 설명을 위해 필요한 부분만 남긴 일부 코드입니다. ```java @Component public class OrderFacade { private final WalletService walletService; private final ProductService productService; private final OrderService orderService; private final OrderItemService orderItemService; private final DataPlatform dataPlatform; public void place(long userId, long productId, int quantity) { walletService.minusBalance(wallet, totalPrice); productService.updateStock(product, quantity); val savedOrder = orderService.order(userId); dataPlatform.send(savedOrder); } } ``` ## @EventListener를 사용하면? - 특정 이벤트 발생 시에만 관련 작업이 실행되도록 분리할 수 있다. ### @EventListener를 사용해서 로직 분리를 해보자 ```java // 1. 이벤트 클래스 생성: 주문이 완료됐음을 알리는 이벤트 객체 생성 public class OrderPlacedEvent { private final Order order; public OrderPlacedEvent(Order order) { this.order = order; } public Order getOrder() { return order; } } // 2. OrderFacade 수정: OrderFacade는 주문 저장까지의 로직만 처리하고 그 이후의 로직은 이벤트로 처리하도록 수정한다. @Component public class OrderFacade { private final WalletService walletService; private final ProductService productService; private final OrderService orderService; private final ApplicationEventPublisher eventPublisher; public void place(long userId, long productId, int quantity) { walletService.minusBalance(wallet, totalPrice); productService.updateStock(product, quantity); val savedOrder = orderService.order(userId); // 이벤트 발행 eventPublisher.publishEvent(new OrderPlacedEvent(savedOrder)); } } // 3. 이벤트 리스너로 후속 작업 처리: DataPlatform과 같은 후속 작업은 별도의 @EventListener 메서드에서 처리한다 @Component public class DataPlatformListener { @EventListener public void handleOrderPlacedEvent(OrderPlacedEvent event) { // 데이터 플랫폼에 주문 전송 val order = event.getOrder(); log.info("Sending order to data platform: {}", order) // 실제 전송 로직 } } ``` ## 결과: 의존성 분리 효과 ### 1. 단일 책임 원칙 준수 OrderFacade는 주문 관련 로직만 처리하고, 후속 작업은 분리된 클래스에서 담당하므로 응집도가 높아진다. ### 2. 결합도 감소 OrderFacade는 DataPlatform에 직접 의존하지 않으므로, 데이터 전송 방식이 변경되어도 OrderFacade를 수정할 필요가 없다. ### 3. 확장성 증가 새로운 후속 작업이 필요할 경우, 단순히 새로운 이벤트 리스너를 추가하기만 하면 된다. - e.g. 주문이 완료된 후 사용자의 이메일로 주문 확인 알림을 전송해야 하는 요구사항이 추가되었을 때 @EventListener를 사용해서 새로운 리스너를 구현만 하면 된다.(**OrderFacade나 다른 기존 코드를 전혀 수정하지 않아도 된다!**) ### 4. 비동기 처리 가능 Spring의 이벤트는 기본적으로 동기적으로 실행되지만, 필요에 따라 비동기 이벤트로 전환하여 성능을 더욱 향상시킬 수 있다. ```java @Async @EventListener public void handleOrderPlacedEvent(OrderPlacedEvent event) { // 비동기 처리 로직 } ``` 이와 같은 설계는 모듈 간의 의존성을 줄이고, 코드의 확장성과 유지보수성을 높이는 데 유용하다.