# 스프링의 @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) {
// 비동기 처리 로직
}
```
이와 같은 설계는 모듈 간의 의존성을 줄이고, 코드의 확장성과 유지보수성을 높이는 데 유용하다.