Spring Event

SexyWoong·2024년 10월 25일

우이삭 프로젝트에서 Member 프로필을 수정하는 경우에 Event를 사용했습니다.
그 이유는 updateMemberProfile메서드의 경우, 회원의 profile을 수정하는 책임을 갖습니다. Transaction이 커밋된 후의 Cache를 무효화하는 로직은 updateMemberProfile에서 하는 것이 아닌 다른 객체의 책임이라고 생각했습니다. 하나의 객체에서 두개의 책임을 갖는 것은 복잡성을 높인다고 생각하여 Event를 사용하여 분리하였고, Event에 대해 학습한것을 정리하는 목적이 있습니다.

MemberService.class의 일부

@Transactional
@CacheEvict(value = {CacheConstants.MEMBER_PROFILE, CacheConstants.COUPLE_PROFILE}, key = "#memberId")
public void updateMemberProfile(List<MultipartFile> profileImages, MemberProfileEditServiceRequest serviceRequest, Long memberId) {
     Member findMember = validateMemberId(memberId);
     String oldProfileImageUrl = findMember.getImageUrl();

     String profileImageUrl = updateProfileImage(profileImages, oldProfileImageUrl);

     updateMemberProfile(profileImageUrl, serviceRequest, findMember);
     Events.raise(new MemberUpdatedEvent(findMember));
}

EventListener

@Profile("!test")
@Component
@RequiredArgsConstructor
public class MemberCreatedEventListener {

    private final CacheManager cacheManager;

    @Async
    @TransactionalEventListener(value = MemberUpdatedEvent.class, phase = TransactionPhase.AFTER_COMMIT)
    public void handleAfterMemberUpdateInvalidateCache(MemberUpdatedEvent event) {
        Member member = event.member();
        Objects.requireNonNull(cacheManager.getCache(CacheConstants.USER_DETAILS)).evict(member.getEmail());
    }
}

Spring Framework의 Event란 무엇인가

Spring Framework의 Event 시스템은 애플리케이션 내에서 객체 간의 느슨한 결합을 유지하면서 메시지를 전달할 수 있도록 돕는 메커니즘입니다. 애플리케이션의 구성 요소가 서로 직접적인 참조 없이 이벤트를 발생시키고 해당 이벤트에 반응할 수 있게 해줍니다. 이를 통해 모듈 간의 독립성을 유지하면서도 기능적 협업을 가능하게 합니다.

핵심 요소

Event

이벤트는 특정 상황이 발생했을 때 이를 알리기 위한 객체입니다. 일반적으로, Spring에서 이벤트는 ApplicationEvent 클래스를 상속하거나 POJO(Plain Old Java Object)로도 표현될 수 있습니다.

예시

public class MyEvent {
    private String message;

    public MyEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

Event Publisher

  • 이벤트 퍼블리셔는 애플리케이션에서 이벤트를 발생시키는 역할을 합니다. Spring에서는 ApplicationEventPublisher 인터페이스를 통해 이벤트를 퍼블리싱할 수 있습니다.
  • ApplicationEventPublisher 인터페이스를 구현한 클래스는 publishEvent() 메서드를 사용하여 이벤트를 발생시킵니다.

예시

@Component
public class MyEventPublisher {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publish(String message) {
        MyEvent event = new MyEvent(message);
        eventPublisher.publishEvent(event);
    }
}

EventListener

  • 이벤트 리스너는 특정 이벤트를 수신하고 해당 이벤트가 발생했을 때 이를 처리하는 역할을 합니다. Spring에서는 @EventListener 애노테이션을 통해 간단하게 리스너를 구현할 수 있습니다.
  • 리스너는 이벤트가 발생했을 때 자동으로 트리거되며, 메서드에 이벤트 타입을 지정하여 특정 이벤트를 수신할 수 있습니다.

예시

@Component
public class MyEventListener {
    @EventListener
    public void handleMyEvent(MyEvent event) {
        System.out.println("Received event: " + event.getMessage());
    }
}

참고) EventListener의 매개변수에 해당하는 클래스 타입의 이벤트가 발생했을 때, EventListener가 작동한다.

Async

  • 기본적으로 이벤트 리스너는 동기적으로 동작합니다. 하지만 이벤트 리스너를 비동기적으로 처리하고 싶다면 @Async 애노테이션을 추가하면 됩니다.
  • 비동기 처리를 위해서는 Spring의 비동기 기능을 활성화해야 합니다. @EnableAsync 애노테이션을 사용하여 설정합니다.

예시

@Configuration
@EnableAsync
public class AsyncConfig {
}

@Component
public class MyAsyncEventListener {
    @Async
    @EventListener
    public void handleMyEvent(MyEvent event) {
        System.out.println("Received event asynchronously: " + event.getMessage());
    }
}

트랜잭션과 이벤트

  • Spring에서는 트랜잭션 경계 내에서 이벤트를 발생시키고 관리할 수 있습니다. 트랜잭션이 완료된 후에 이벤트를 발생시키고 싶다면 @TransactionalEventListener 애노테이션을 사용할 수 있습니다.
  • @TransactionalEventListener는 트랜잭션이 성공적으로 커밋된 후에 이벤트를 처리하는 기능을 제공합니다. 기본적으로 트랜잭션 커밋 후 이벤트를 처리하지만, phase 속성을 사용하여 다양한 트랜잭션 상태에서 이벤트를 처리할 수 있습니다.

예시

@Component
public class MyTransactionalEventListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleMyEvent(MyEvent event) {
        System.out.println("Received event after transaction commit: " + event.getMessage());
    }
}

Spring Event의 장점

1. 모듈 간 느슨한 결합

이벤트 시스템을 통해 모듈 간의 의존성을 최소화하면서 메시지를 전달할 수 있습니다. 퍼블리셔는 리스너에 대해 알 필요가 없고, 리스너 역시 퍼블리셔에 대해 알 필요가 없습니다.

2. 유연한 비동기 처리

동기적으로 이벤트를 처리할 수도 있고, 비동기적으로 처리할 수도 있어 애플리케이션의 성능과 확장성을 향상시킬 수 있습니다.

3. 트랜잭션과의 연계

트랜잭션과 이벤트를 연계하여 트랜잭션 커밋 후 또는 트랜잭션 롤백 후 이벤트를 처리하는 등, 비즈니스 로직의 복잡성을 쉽게 관리할 수 있습니다.

4. 확장성 및 유지보수성

새로운 기능을 추가할 때 기존 코드의 수정 없이 리스너를 추가하여 확장할 수 있어 유지보수성이 뛰어납니다.

profile
함께 있고 싶은 사람, 함께 일하고 싶은 개발자

0개의 댓글