Spring AOP를 활용하여 서비스 로직의 시간 체크하는 @Annotation 만들기

안녕하세요. 저는 현재 java/spring 환경에서 upbit API를 활용하여 트레이딩 시스템을 개발하고 있습니다.

 

트레이딩 시스템은 현재 1차 배포까지 완성이 되어 트레이딩을 진행하고 있으며, 그간에 있었던 내용중 공유하고자 하는 내용이 있어 글을 작성합니다.

 

현재 트레이딩 시스템의 가장 주요한 난제는 알고리즘 최적화입니다.

 

적절한 알고리즘에 따라 필요하지 않은 로직은 수행하지 않고 시간을 많이 잡아먹는 로직, 혹은 코인에 대해 트래킹이 필요하고 왜 그랬는지 대처를 해야 좀더 적절한 대응이 가능하게 됩니다.

 

그 중 가장 기본은 각 코인당 발생하는 동일한 service를 태웠을 때 허용 가능한 시간동안의 로직을 수행했느냐를 체크하는 것이 필요했습니다.

 


@Slf4j
@RequiredArgsConstructor
@Component
public class TradeTask {

    private final Trader trader;
    private final SlackMessageService slackMessageService;

    @Scheduled(cron = "5 */5 * * * *")
    public void collectGetCoinFiveMinutesCandles() {
        slackMessageService.sendTraderMessage("====== 트레이딩 시작 =======");
        trader.run();
        slackMessageService.sendTraderMessage("====== 트레이딩 종료 =======");
    }

}

(위의 예제는 전체 서비스 로직의 수행시간을 체크합니다.)

 

현재 trader 클래스의 run 메소드를 통해서 매 5분마다 서비스 로직을 수행합니다. 안에는 Upbit API와 HTTP 통신하는 client 클래스들이 다수 통신하고 있고, 이는 block 방식입니다.(이 부분은 초당 호출 가능한 upbit API 호출 갯수 제한에 의해 block 방식을 활용합니다.)

 

서비스의 시작과 종료가 하나의 스레드 흐름으로 동작할 것이고 종료 타이밍이 가장 늦게 적용되는 알고리즘입니다. 5분마다 돌아야 하기때문에 해당 로직은 5분 이내로 종료되어야 합니다.

 

따라서 저는 아래의 어노테이션을 설계하였습니다.

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeCheck {
}

사실 굉장히 간단합니다. @Retention 어노테이션은 해당 어노테이션을 런타임까지 가져가기 위해(aop를 활용하기 위해서는 필수입니다.) 설정해놓고, @Target은 메소드 상단에 어노테이션을 활용할 것이기 때문에 METHOD로 두었습니다.

 

이는 아래와 같이 활용합니다.

 

@TimeCheck
public void run() {
	// upbit api 초당 8회, 분당 200회
	// .. trading service 호출
    // .. upbit api client 호출
    // .. 통계 및 로그에 필요한 데이터 저장.
    // 등등.
}

그럼 스프링이 해당 annotion을 어떻게 읽어서 로직의 시간을 측정할 수 있을까요? 단순 어노테이션 클래스만 만들면 되는 걸까요??

 

당연히 아닙니다.

 


결론만 말씀드리자면 bean 프록시의 참조에 의해서 호출될 때, 해당 어노테이션을 인식하기만 하면 됩니다. @Transactional 어노테이션을 사용할 때랑 비슷합니다. 

 

저는 아래와 같이 어노테이션을 활용하였습니다.

 

@Slf4j
@Aspect
@Component
public record TimeCheckAspect(SlackMessageService slackMessageService) {

    @Around("@annotation(com.blackdog.trader.config.annotation.TimeCheck)")
    public Object handlerTimeCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long end = System.currentTimeMillis();
        slackMessageService.sendTraderMessage(String.format("[runTime : %ss] - 시간(UTC) : %s 매매 완료", (end - start)/1000, LocalDateTime.now().plusHours(9)));
        return proceed;
    }

}

@Around 어노테이션을 통해 @TimeCheck 어노테이션의 시작과 끝지점을 제어할 수 있고 ProceedingJoinPoint 객체에 의해 서비스 로직의 호출을 원하는 시점에 통제할 수 있습니다.

따라서 단순히 어떤 큰 틀같은 바구니 안에 로직을 넣는다로 이해하면 좋을 것 같습니다.

 


일단 오늘은 예제만 간단히 소개하였고 aop에 대한 코드는 별도로 까보는 시간을 가져보겠습니다. bean 프록시에 참조에 의한 호출이면 어떤 서비스 로직이던 측정할 수 있으니 AOP라는 이름에 겁을 먹거나 귀찮아할 필요는 없을 것 같습니다.

 

감사합니다.

댓글

Designed by JB FACTORY