ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring의 @EventListener
    Spring 2023. 1. 30. 01:43

    이벤트

    Spring의 @EventListener를 쓰는 이유는, 의존성이 강한 로직들을 분리하기 위해서이다.

     

    애플리케이션을 만들다보면 서로 다른 서비스 간에 의존성이 발생하는 경우가 자주 생긴다.

    @Service
    public class StudyGroupService {
    
      public void create(String name) {
        // 스터디 생성 로직
        System.out.println("스터디 생성 완료");
        
        // 스터디 생성 메시지 전송
        System.out.println(name + "스터디가 생성되었습니다.");
      }
    }

    스터디를 생성하면 스터디 생성 완료 메시지 전송해주는 위의 코드는 몇가지 문제점이 존재한다.

     

    1. 강한 결합

    현재 스터디 생성 로직, 스터디 생성 메시지 전송 로직이 섞여있다. 이렇게 서로 강한 의존성을 가지고 있으면 추후에 유지보수가 어려울 뿐만 아니라 코드의 구조가 복잡해진다.

     

    2. 트랜잭션

    만약 스터디 생성 메시지를 전송하는 중 예외가 발생했을 때 스터디 생성 처리 까지 모두 롤백을 하는 것은 좋은 방법이 아니다.

    스터디 생성 처리를 해주고, 메시지 전송은 따로 관리하는 게 좋다.

     

    3. 성능

    메인 이벤트인 스터디 생성 로직을 끝내면 서브 이벤트인 메시지 전송은 전송 완료까지 기다릴 필요가 없다.

    즉, 스터디 생성 -> 스터디 생성 메시지 전송 -> 스터디 생성 완료 가 아닌 스터디 생성 -> 스터디 생성 완료 (메시지 전송은 스터디가 생성 되면 따로 실행)의 순서로 실행을 하면 된다.

    이러한 상황을 이벤트를 적용하여 해결할 수 있다. 이벤트는 생성 주체의 상태가 변경되면 이벤트를 발생시켜 원하는 기능을 실행해서 후처리를 도와준다.

     

    이벤트 실행 단계

    1. 생성 주체에서 이벤트가 발생하면 이벤트 디스패처에게 전달한다.

    2. 이벤트 디스패처가 이벤트 핸들러를 연결해준다.

    3. 이벤트 핸들러에서 이벤트에 담긴 데이터를 통해 원하는 기능을 실행한다.

     

    구현

    이벤트 클래스 만들기 - StudyCreatedEvent

    @Getter
    public class StudyCreatedEvent{
        private StudyGroup studyGroup;
    
        public StudyCreatedEvent(StudyGroup studyGroup) {
            this.studyGroup = studyGroup;
        }
    }

     

    서비스 만들기 - StudyGroupService

    @Service
    public class StudyGroupService {
    
      @Autowired
      ApplicationEventPublisher publisher; // 1
    
      public void create(StudyGroup studyGroup) {
        // 스터디 생성 로직
        System.out.println("스터디 생성 완료");
        publisher.publishEvent(new StudyCreatedEvent(newStudyGroup)); // 2
      }
    }

    (1) 이벤트를 보내는 기능을 사용하기 위해 ApplicationEventPublisher를 주입해준다.

    (2) 스터디 생성이 완료되면, publishEvent()를 사용해 이벤트를 전달해준다.

     

     

    이벤트 핸들러 만들기 - studyEventListener

    public class StudyEventListener {
        private final UserRepository userRepository;
        private final NotificationRepository notificationRepository;
    
        @EventListener
        public void handleStudyCreatedEvent(StudyCreatedEvent studyCreatedEvent){
            StudyGroup studyGroup = studyCreatedEvent.getStudyGroup();
            Iterable<User> users = userRepository.findAll(UserPredicates.findByLocationAndSkills(studyGroup.getLocation(), studyGroup.getSkills()));
            users.forEach(user -> {
                saveStudyCreatedNotification(studyGroup, user);
            });
        }

    @EventListener를 사용하면 이벤트 리스너로 등록이 되고, 매개변수에 이벤트 클래스를 정의하면 해당 이벤트가 발생했을 때 수신해서 처리를 할 수 있다.

     

    컨트롤러 만들기 

    public class TestController {
    
      @Autowired
      StudyGroupService service;
    
      @GetMapping("/create/{studyGroupId}")
      public void register(@PathVariable Long studyGroupId) {
      	StudyGroup studyGroup = studyGroupRepository.findById(studyGroupId);
        service.create(studyGroup);
        System.out.println("스터디 생성 완료");
      }
    
    }

     

    비동기 처리

    만약 DB INSERT, 서버로 전송 등으로 이벤트 리스너가 시간이 오래걸리는 작업이 생기는 경우 비즈니스 로직 전체의 실행이 밀리게 된다. 이런 경우 비동기 처리를 해주면 성능을 향상시킬 수 있다.

    @Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {
    
        @Override
        public Executor getAsyncExecutor(){
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            int processors = Runtime.getRuntime().availableProcessors();
            executor.setCorePoolSize(processors);
            executor.setMaxPoolSize(processors * 2);
            executor.setQueueCapacity(50);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("AsyncExecutor-");
            executor.initialize();
            return executor;
        }
    }

    비동기 처리를 위해 Application에 @EnableAsync를 추가해주거나,

    config 파일을 생성하여 @EnableAsync 어노테이션을 설정해준다.

     

    그런 다음 비동기로 처리할 메소드에 @Async를 추가해준다.

    @Component
    public class StudyEventListener {
    
        @Async
        @EventListener
        public void handleStudyCreatedEvent(StudyCreatedEvent studyCreatedEvent){
            StudyGroup studyGroup = studyCreatedEvent.getStudyGroup();
            Iterable<User> users = userRepository.findAll(UserPredicates.findByLocationAndSkills(studyGroup.getLocation(), studyGroup.getSkills()));
            users.forEach(user -> {
                saveStudyCreatedNotification(studyGroup, user);
            });
        }
    }

    비동기로 처리함으로써 스터디가 생성되면, "스터디 생성 완료" 메시지가 먼저 뜨고, 그 이후에 스터디 생성 알림을 전송할 수 있게 된다.

     

     

     

    참고

    https://shinsunyoung.tistory.com/88

Designed by Tistory.