Let's Enjoy Spring Event

Panda·2025년 4월 20일
0

Spring

목록 보기
46/46

Spring에서 Event를 사용하여
소스코드에 Event 개념을 녹일 수 있는 것이 있어서 공부하려고 합니다.

Spring Event

기본적인 개념인 발행 및 구독 Event 기본사상이 Spring에서 활용할 수 있는 구조로 되어있습니다

발행

Event 발행에 앞서 실제 발행할 Event를 명시를 해야하는데 아래와 같습니다.


// 스프링 4.2 이후로는 ApplicatiopEvent 상속 안받아도 됩니다.
@Getter
public class UserChangedEvent extends ApplicationEvent {
    
    private User user;
    
    // ...
    
    public UserChangedEvent(Object source, User user) {
        super(source);
        this.user = user;
    }
    
}
public void modifyUser(UserModifyRequest request) {
	// ... 
    // ... user 생성 로직
    applicationEventPublisher.publishEvent(new UserChangedEvent(this, user));
}

발행 자체는 되게 간단하게 발행이 가능합니다.

구독

발행보다는 이벤트를 받아 처리하는 구독쪽이 좀 더 핵심인데요

@Component
public class UserEventListener {

    @EventListener
    public void userChangedEventHandler(UserChangedEvent event) {

    }
}

일단 기본적으로 @Async 어노테이션이 안달려있으면 동기적으로 작동합니다.
밑에는 동기적으로 동작하는 EventListner 예제입니다.

@Transactional
public void modifyUser(UserModifyRequest request) {
	log.info("user modify start");
	// ... 
    // ... user 생성 로직
    applicationEventPublisher.publishEvent(new UserChangedEvent(this, user));
    
    log.info("user modified");
}

public class UserEventListener {

    @EventListener
    public void userChangedEventHandler(UserChangedEvent event) {
    	log.info("user modify history save");

    }
}

출력결과
user modify start
user modify history save
user modified

하지만 위에 EventListner는 Transaction과 전혀 상관없이 수행한다는 점입니다.
예를들어 UserChangedEvent 발행이후 user point 관련 로직이 있고 해당 구간에서 에러가 발생한다고 가정해보겠습니다.

@Transactional
public void modifyUser(UserModifyRequest request) {
	log.info("user modify start");
	// ... 
    // ... user 생성 로직
    applicationEventPublisher.publishEvent(new UserChangedEvent(this, user));
    
    // user point 관련 로직
    userPoint(user);  // 에러 발생!!
    
    log.info("user modified");
}

위와 같은 상황일 때는 롤백이 일어남에도 불구하고
UserChangedEvent 에서 발생한 user history 저장은 롤백이 안됩니다.

그러면 어떠한 방법이 있는 것 이냐?
다행스럽게도 Spring Event 에서는 이벤트 처리를 Transaction에 녹일 수 있도록 지원을 하였습니다.

@TransactionalEventListener

  • BEFORE_COMMIT: 발행자의 트랜잭션 커밋 직전에 처리
  • AFTER_COMMIT: 발행자의 트랜잭션 커밋 직후 처리
  • AFTER_ROLLBACK: 발행자의 트랜잭션 롤백 직후 처리
  • AFTER_COMPLETION: 발행자의 트랜잭션 커밋/롤백 직후 처리
public class UserEventListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
	@Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    public void userChangedEventHandler(UserChangedEvent event) {
    	log.info("user modify history save");
    }
}

위의 코드는 본래의 Transaction commit 이후에 발생되며
새로운 Transaction이 생성되어 기존 스레드와 다른 스레드에서 동작을 하는 EventListner 입니다.

그렇기 때문에 user point 에서 에러가 발생하여 롤백이 이루어진다면
해당 이벤트 리스너는 작동을 안합니다.

@Transactional(propagation = Propagation.REQUIRES_NEW)
를 해주는 이유는 이미 기존 Transaction 이 commit 이 끝났기 때문에 더 이상 변경을 못하기 때문에 신규 Transaction 을 생성해 작업을 진행해줍니다.

또한 UserChangedEvent 성격에서는
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
이 코드가 가진 의미가 더 중요한데
원래 데이터가 정상적으로 DB에 반영이 된 이후에 작동된다 라는 데이터 무결성을 지켜주는 것이기도 합니다.

EventListner 같은 경우는
해당 이벤트가 발행자의 트랜잭션과 관계가 있어야하는건지?
기존 로직과 영향이 전혀 없어야하는건지? 영향이 있어야하는건지?
를 잘 고려하여 해당 목적에 맞는 어노테이션을 적용하시면 될 것 같습니다.

Spring Event 언제 써야하나

제가 해당 기능을 실무에 사용하려고 한 이유는

  • 기존 코드에 영향을 안줘야함
  • 서비스에서 이벤트 발생! 이라는 도메인 행동을 개발에서도 표현하고 싶었음
    • 코드에 이벤트를 표현함으로써 도메인 별 분리가 보이기 떄문 -> 서비스 이해도가 높아짐--> 서비스 품질이 높아질 확률이 높음

느낀점

Spring Event를 쓰게 되면서 확 와닿은 특징이 있었는데

기존 Context에 영향없게끔 코드를 작성할 수 있다!라는 특징도 있지만
이건 기존 @Asnyc 라던지 reactive 프로그래밍이라던지 다양한 방법으로도 가능한 특징이기 때문에
Spring Event 만의 특색은 아니였습니다.

저 같은 경우는
Event 개념을 소스코드 상에 녹여 표현을 할 수 있다가 가장 큰 장점이라고 느꼈습니다.

Event라는 개념은 서비스에서 발생하는 모든 행위인데
이를 개발에서도 표현을 할 수 있어서 좀 더 서비스를 이해하고 개발에 많은 도움을 주는 것 같습니다.

좀 더 확장하면 DDD 개념이라던지 도메인 측 과도 연관을 지을 수 있어서 좋은 것 같습니다.

참고 사이트

profile
실력있는 개발자가 되보자!

0개의 댓글