본문 바로가기

Spring Boot/비동기 처리

Spring Event : 이벤트 기반 비동기 처리 활용사례

캐시갱신, 외부시스템 연동에 대해서


이벤트 기반 비동기 처리의 핵심 아이디어

이벤트 기반 비동기 처리는 핵심 로직과 부가 로직을 분리하는 데 초점을 둔다.

 

 - 회원가입 시 DB에 회원 등록은 핵심 로직이다.

 - 환영 이메일 발송, 가입 로그 기록, 추천인 포인트 적립 이런것들은 부가 로직이다.

 

이런 부가 로직들을 이벤트 리스너로 분리하고 비동기로 실행하면, 시스템은 더욱 빠르고 유연해지며 유지보수가 쉬워짐!

 

1. 이벤트 기반 처리의 구조

이벤트 기반 비동기 구조의 전형적인 흐름

 

중요한건 Publisher - Event - Listener로 이어진다는 것!


캐시 갱신 처리

데이터 변경시 실시간으로 캐시를 갱신하여 최신 데이터를 유지할 수 있다고 함

 

1. 이벤트 정의

public class CacheUpdateEvent {
	private final String key;
    public CacheUpdateEvent(String key) {
    	this.key = key;
    }
    public String getKey() {
    	return key;
    }
}

 

 

2. 캐시 갱신 퍼블리셔

@Service
public class ProductService {
	private final ApplicationEventPublisher publisher;
    
    public ProductService(ApplicationEventPublisher publisher) {
    	this.publisher = publisher;
    }
    
    public void updateProduct(String productId) {
    	System.out.println("상품 정보 수정 완료");
        publisher.publishEvent(new CacheUpdateEvent(productId));
    }
}

 

 

3. 캐시 리스너

@Component
public class CacheUpdateListener {
	@Async
    @EventListener
    public void refreshCache(CacheUpdateEvent event) {
    	System.out.println("캐시 갱신중 : " + event.getKey());
        try {
        	Thread.sleep(1000);
            System.out.println("캐시 갱신 완료 : " + event.getKey());
        } catch(InterruptedException e) {
        	Thread.currentThread().interrupt();
        }
    }
}

 


외부 시스템 연동(Slack, Kafka, REST API, 등등..)

이벤트를 외부 시스템으로 전달하면 시스템간 통합이 가능하다고 함

 

1. 이벤트 정의

public class ExternalIntegrationEvent {
	private final String payload;
    public ExternalIntegrationEvent(String payload) {
    	this.payload = payload;
    }
    public String getPayload() {
    	return payload;
    }
}

 

 

2. 외부 연동 리스너

@Component
public class ExternalApiEventListener {
	@Async
    @EventListener
    public void callExternalSystem(ExternalIntegrationEvent event) {
    	System.out.println("외부 시스템 호출 준비 : " + event.getPayload());
        try {
        	Thread.sleep(2000);
            System.out.println("외부 시스템 호출 성공 : " + event.getPayload());
        } catch(InterruptedException e) {
        	Thread.currentThread().interrupt();
        }
    }
}

 


상황
리스너 로직이 복잡할때 별도 서비스 클래스로 분리하여 단일 책임을 유지하기
비동기 이벤트 예외 처리 @Async 메서드 내 try - catch로 로깅 및 복구 처리
테스트시 비동기 실행 제어 TaskExecutor를 동기 실행기로 교체하기
이벤트 중복 실행 방지 이벤트 ID나 타임스탬프를 부여하여 중복 검사하기
외부 API 호출 실패시 재시도 로직 또는 Dead Letter Queue 설계하기....