ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 템플릿 콜백 패턴 - 템플릿 패턴, 전략 패턴의 최종
    스프링/스프링 AOP 2023. 9. 5. 17:45

    콜백의 정의

    프로그래밍에서 콜백 또는 콜애프터 함수는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다.

     

    코드가 호출이 되기는 하지만 코드를 넘겨준 곳의 뒤에서 실행된다는 뜻이다.

     

    이전의 전략패턴에서 예시를 들면 클라이언트에게 직접 Strategy를 실행하는 것이 아니라, 클라이언트가 ContextV2.execute()를 실행할 때 Strategy를 넘겨주고, Context2 뒤에서 Strategy가 실행되는 것이다.

     

     

    템플릿 콜백 패턴은 GOF 패턴은 아니고, 스프링 내부에서 이런 방식을 자주 사용하기에 스프링에서만 있는 패턴이다.

     

     

    Callback 인터페이스

    public interface Callback {
        void call();
    }

     

     

    Callback 인터페이스의 구현체인 TimeLogTemplate

    @Slf4j
    public class TimeLogTemplate {
    
        public void execute(Callback callback) {
            long startTime = System.currentTimeMillis();
            //비즈니스 로직 실행
            callback.call(); //위임
            //비즈니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime={}", resultTime);
        }
    }

     

     

     

     

    테스트에서의 템플릿 콜백 패턴

    @Slf4j
    public class TemplateCallbackTest {
    
        /**
         * 템플릿 콜백 패턴 - 익명 내부 클래스
         */
        @Test
        void callbackV1() {
            TimeLogTemplate template = new TimeLogTemplate();
            template.execute(new Callback() {
                @Override
                public void call() {
                    log.info("비즈니스1 로직 실행");
                }
            });
    
            template.execute(new Callback() {
                @Override
                public void call() {
                    log.info("비즈니스2 로직 실행");
                }
            });
        }
    }

    코드를 보면 알겠지만 저번 포스트의 전략 패턴의 아래부분은 V2 버전이 템플릿 콜백 패턴이다. 공통 부분은 인터페이스의 구현체에 있고, 애플리케이션 로딩 시점에 정해지는 콜백 패턴이다. 코드는 저번 포스트와 너무 비슷하기 때문에 따로 설명을 하지는 않겠다.

     

     

     

     

    실제 애플리케이션에 적용하기

     

    1. TraceCallback 인터페이스 생성

    public interface TraceCallback<T> {
        T call();
    }

     

     

    2. TraceTemplate 구현

    public class TraceTemplate {
    
        private final LogTrace trace;
    
        public TraceTemplate(LogTrace trace) {
            this.trace = trace;
        }
    
        public <T> T execute(String message, TraceCallback<T> callback) {
            TraceStatus status = null;
            try {
    
                status = trace.begin(message);
    
                // 로직 호출
                T result = callback.call(); // callback에서 넘어온 거에서 call 메서드 호출
    
                trace.end(status);
                return result;
            } catch (Exception e) {
                trace.exception(status, e);
                throw e; // 예외를 꼭 다시 던져주어야 한다.
    
            }
        }
    }

     

     

    3. 각 계층(컨트롤러, 서비스, 리포지토리)에 적용

    @RestController
    public class OrderControllerV5 {
    
        private final OrderServiceV5 orderService;
        private final TraceTemplate template;
    
        public OrderControllerV5(OrderServiceV5 orderService, LogTrace trace) {
            this.orderService = orderService;
            this.template = new TraceTemplate(trace);
        }
    
        @GetMapping("v5/request")
        public String request(String itemId) {
            return template.execute("OrderController.request()", new TraceCallback<>() {
                @Override
                public String call() {
                    orderService.orderItem(itemId);
                    return "ok";
                }
            });
        }
    }

    @RequiredArgsConstructor 어노테이션을 빼고 생성자로 주입해줬는데, 저렇게 해주면 TraceTemplate을 싱글톤으로 사용할 수 있기 때문이다. Service와 Repository도 같으니 Controller로 설명하겠다. 컨트롤러의 함수 안에 template.execute를 사용한다. template의 execute()메서드는 파라미터로 message와 callback을 받아오는데, 메시지와 new TraceCallback<>을 통해 전략패턴의 코드를 깔끔하고 유연하게 설계할 수 있었다.

Designed by Tistory.