공부내용공유

Spring event 올바른 사용법 본문

Spring/Event

Spring event 올바른 사용법

forfun 2024. 1. 14. 16:01

 

서론


지금 진행중인 프로젝트에서 어떤 기능이 실패했을 때 로그성 정보를 DB에 저장하는 기능을 추가해야했다.

 

처음에 아무 생각없이 다른 service 계층에서 저장 로직을 가지고와 사용했는데 이러한 리뷰를 받았다.

 

로그 저장은 해당 기능의 주 관심사는 아닌거 같아요! event,aop,interceptor와 같은 기능을 사용해서 주 관심사와 부 관심사를 분리하는게 어떨까요?

 

완전 맞는 말이었고 해당 기능을 event를 통해 분리하였다.

 

사실 event라는 개념을 처음 사용해봐서 검색을 하고 비슷한 방식으로 적용하였는데 그렇게 사용하는 것은 event의 본질을 살리지 못했다 라는 리뷰를 받았고 event에 대해 제대로 공부하게 되었다.

 

이 글에서는 코드의 변화 과정을 기반으로 spring event의 올바른 사용법에 대해 작성할 예정이다.

본론


  • 임의로 예시 코드를 작성하여 설명을 할 것이다. 다른 부분은 크게 신경 쓸 필요없고 예외 발생시 로그저장 기능만 보면된다!

맨 처음 코드

public void sendMessage(MessageRequest messageRequest){
	 
		Message message = messageRequest.to();
		
		messageValidator.validateType(message);

		try{
			messageSender.send(message);
		}
		catch(MessageSendFailException e){
			Log log = logDto.from(message);	
			logService.save(log);
		}
}

 

이런식으로 특정 로직이 실패했을경우 추후 벌크처리, 원인 파악을 위해 유의미한 정보들을 DB에 저장하는 로직이었다.

 

이 코드의 문제점은 무엇일까?

 

sendMessage의 책임은 메세지를 보내는 것이다. 즉  주 관심사는 메세지 전송이고 실패했을 때 log를 저장하는 것은 부 관심사 이다.

 

이런식으로 여러 부 관심사 코드를 계속하여 한가지 메소드에 넣게되면 결국에는 절차지향적인 코드가 될 것이다.

 

이러한 부분이 내가 받은 리뷰였고 이를 객체지향적인 코드로 개선하기 위해 spring의 event를 사용하였다.

 

spring event의 사용법은 다른 블로그에도 많이 다루고 있고 사용법 자체는 굉장히 쉽다.

 

내가 처음에 작성했던 코드로 간단하게 예시를 들면

private final ApplicationPulbisher publisher;

public void sendMessage(MessageRequest messageRequest){
	 
	Message message = messageRequest.to();
		
	messageValidator.validateType(message);

	try{
		messageSender.send(message);
	}
	catch(MessageSendFailException e){			
		SaveLogEvent saveLogEvent = SaveLogEvent.from(message);
		publisher.publishEvent(saveLogEvent);
	}
}

@RequiredArgsConstructor
@Component
public class LogEventHandler{
		
	private final LogRepository logRepository;

	@EventListener
	public void saveLog(SaveLogEvent event){

		Log log = event.to();

		logRepository.save(log);

	}
}

 

이렇게 event를 보낼 클래스에는 spring 이 구현해 놓은 ApplicationPulbisher 를 주입 받아서 publishEvent 라는 메서드를 사용해서 event를 보내는 형태이다.

 

또 event 받는 메소드는 @EventListener를 사용하여 event를 받고 해당 메서드에서 원하는 작업을 하면된다.

 

자세한 사용법은 다른 검색하면 잘 나와있다!

 

 

event를 사용하여 messageService에서 더이상 logService를 의존하지 않아도 되긴한다. 딱 그뿐이지 이것이 진짜 관심사가 분리된거라고 할 수 있을까?

 

아니였다. 위 코드는 아직도 sendMesaage 메서드에서 log를 저장한다는 기능을 알고 있다.

 

즉 논리적인 의존이 남아있는 것이다.

 

그렇다면 이를 어떻게 제거할 수 있을까? 제거한 코드를 보자!

private final ApplicationPulbisher publisher;

public void sendMessage(MessageRequest messageRequest){
	Message message = messageRequest.to();
	messageValidator.validateType(message);

	try{
		messageSender.send(message);
	}
	catch(MessageSendFailException e){		
		SendMessageEvent sendMessageEvent = SendMessageEvent.from(message);
		publisher.publishEvent(sendMessageEvent);
	}
}

@RequiredArgsConstructor
@Component
public class LogEventHandler{
		
	private final LogRepository logRepository;

	@EventListener
	public void saveLog(SendMessageEvent event){
		Log log = event.to();
		logRepository.save(log);
	}
}

 

위 코드가 올바른 event 사용법이다.

 

이제 더이상 sendMessage는 자신의 주 관심사인 메세지 전송 외에 로그 기록과 같은 부 관심사 를 모른다. 즉, 논리적인 의존성이 사라졌다.

 

이렇게 event를 올바르게 활용하여 결합력이 낮고 응집도가 높은 객체지향적인 코드를 만들 수 있다! 이 글을 보는 다른 분들도 event 잘 활용하여 좋은 프로젝트를 만들기를 바란다!

결론


이번 기회를 통해 spring의 event와 event 방식을 어떻게 사용해야 하는지 더 잘 알게 되었다.

Aop, interceptor 와 같은 방법들과 같이 관심사 분리에 사용할 좋은 도구라고 생각한다.

 

참고자료

https://www.youtube.com/watch?v=b65zIH7sDug&t=794s

https://techblog.woowahan.com/7835/

 

event에 관한 정말 좋은 자료들이다.