Spring @EventListener 를 활용한 Refactoring
<개요>
- 서비스 내에서 로직 처리후 타 서비스 연동을 위해서 Service Call을 다시 하는 구조
- Spring에서 제공하는 ApplicationEvent, ApplicationListener를 활용하여 리팩토링
<내용>
- 현재 서비스내에서 다음과 같이 특정 상황이 발생했을 때 Noti하기 위해서 Slack으로 메시지를 보내는 로직들이 구성되어 있다.
@Async("executorBean")
public CompletableFuture<Boolean> refreshCount(){
log.info("Before Current API Call remains {} ", countCallService.getCallCount());
if(countCallService.getCallCount() > 0){
sendSlackMessageInfo("");
}
List<MetaDto> metaList = filterService.findAllMetas();
sendSlackMessageInfo( String.format("%s %s", metaList.size(), "metas") );
log.info(...);
...
}
private void sendSlackMessageInfo(String text){
log.info(text);
SlackMessage message = SlackMessage
.builder()
.text(text)
.color(SlackMessage.Color.GOOD)
.build();
slackMessageService.postMessage(message);
}
* 문제점
- 비지니스와 관계없는 SlackService와 Dependency가 발생한다.
- Slack 이외에 다른 연동이나 로직이 추가될 수록 코드가 지저분해진다.
- 실제 비지니스 로직과 관계없는 Message 조립에 대한 로직이 포함되고 있다.
<Refactoring #1>
1. Service
@Async("executorBean")
public CompletableFuture<Boolean> refreshCount(){
log.info("Before Current API Call{} ", countCallService.getCallCount());
if(countCallService.getCallCount() > 0){
eventPublisher.publishEvent(new RefreshMetaErrorEvent(""));
}
List<MetaDto> metaList = filterService.findAllMetas();
eventPublisher.publishEvent(new RefreshMetaStartEvent(metaList.size()));
log.info(...);
...
}
Biz Service내에서는 Event만 publish 하도록 변경하였다.
2. EventHandler
public class RefreshMetaEventHandler {
private final SlackMessageService slackMessageService;
@EventListener
public void errorMessage(final RefreshMetaErrorEvent refreshMetaErrorEvent){
final String message = refreshMetaErrorEvent.getMessage();
sendSlackMessageInfo(message);
}
@EventListener
public void startMessage(final RefreshMetaStartEvent refreshMetaStartEvent){
final int count = refreshMetaStartEvent.getCount();
final String message = String.format("%s %s", count, "filters");
sendSlackMessageInfo(message);
}
private void sendSlackMessageInfo(String text){
log.info(text);
SlackMessage message = SlackMessage
.builder()
.text(text)
.color(SlackMessage.Color.GOOD)
.build();
slackMessageService.postMessage(message);
}
}
@Getter
@RequiredArgsConstructor
public class RefreshMetaStartEvent {
private int count;
}
@Getter
@RequiredArgsConstructor
public class RefreshMetaErrorEvent {
private String message;
}
- 상황에 따라 Event를 수신하여 처리하는 로직은 모두 EventHandler로 모아서 구성하였다. (Service Dependency정리)
- slack연동외의 다른 로직을 추가하기 간결한 구조로 정리되었다.
* 남은 문제점
- Event가 많아질 수록 메소드가 늘어난다.
- EventHandler나 SlackMessageService의 경우 각 Message의 전달만을 책임져야 한다.
- Message조립에 대한 로직은 분리되는 것이 맞다.
<Refactoring #2>
1. Abstract Class 및 Concrete Class 정의
public abstract class AbstractRefreshMetaEvent {
public abstract String getMessage();
}
public class RefreshMetaStartEvent extends AbstractRefreshMetaEvent{
private final int count;
@Override
public String getMessage() {
return String.format("%s %s", count, "metas");
}
}
public class RefreshMetaDemoEvent extends AbstractRefreshMetaEvent {
private final String svcColumnName;
private final int count;
@Override
public String getMessage() {
return String.format("%s has %s data",svcColumnName, count);
}
}
public class RefreshMetaErrorEvent extends AbstractRefreshMetaEvent{
private final String message;
}
- 각 Event별로 Message 조립에 대한 역할을 가져가도록 분리한다.
- 필요에 따라서 세부 Event를 추가로 정의하고 각 상황에 맞게 상세구현을 진행할 수 있다.
2. EventHandler
public class RefreshMetaEventHandler {
private final SlackMessageService slackMessageService;
@Async
@EventListener
public void sendMessage(final AbstractRefreshMetaEvent refreshMetaEvent){
final String message = refreshMetaEvent.getMessage();
sendSlackMessageInfo(message);
// 연동 및 로직 추가구성 가능
}
private void sendSlackMessageInfo(String text){
log.info(text);
SlackMessage message = SlackMessage
.builder()
.text(text)
.color(SlackMessage.Color.GOOD)
.build();
slackMessageService.postMessage(message);
}
}
- AbstractRefreshMetaEvent 타입으로 정리하고 Slack으로 전송하는 로직만 남게된다.
- 추가 연동이 발생하거나 로직 수정시 타 소스에 영향도가 낮다.
<정리>
- Event 발행구조 : 해당 Event발생에 따른 후속 상세로직은 Biz Service에서 알 필요가 없다.
- AbstractClass 를 활용하여 추상화하고, 세부 메세지 조립에 대한 로직은 상세클래스에서 구현한다.
- 후속 로직은 요건에 따라 비동기로 처리하여 Biz Logic에 영향이 없도록 한다.
(비동기/넌블러킹 기술스택으로 변경하여 throughput 을 증가시키는 것은 덤!)
- 구조가 기술보다 우선이다.