제육's 휘발성 코딩
article thumbnail
반응형

템플릿 콜백 패턴

콜백(callback)이란 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 의미한다.

콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행하거나 나중에 실행할 수 있다. (call - 호출은 되었고, back - 실행은 뒤에서)

 

@Test
void strategyV2() {
    ContextV2 context = new ContextV2();
    context.execute(new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직1 실행");
        }
    });

    context.execute(new Strategy() {
        @Override
        public void call() {
            log.info("비즈니스 로직2 실행");
        }
    });
}

전략 패턴에서 컨텍스트에 전략을 파라미터로 넘겨주는 방식을 템플릿 콜백 패턴이라 부른다.

Context가 템플릿 역할을 하고, Strategy 부분이 콜백으로 넘어온다고 생각하면 된다.

 

 

템플릿 콜백 패턴은 GoF 패턴은 아니고, 스프링 내부에서 자주 사용되기 때문에 부르게 된 패턴이다.

JdbcTemplate, TransactionTemplate 등이 템플릿 콜백 패턴이다.

 

템플릿 콜백 패턴 - 예제

템플릿 콜백 패턴을 구현해보자.

전략 패턴에서 Context(변하지 않는)Template(변하는 알고리즘) 이 되고, StrategyCallback 이 된다.

 

Callback 인터페이스

public interface Callback {
  void call();
}
  • Strategy에 해당하는 콜백 인터페이스를 생성하자. 인터페이스를 통해 구현체를 유연하게 만들 수 있도록 하자.

 

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);
  }
}
  • Context 에 해당하는 고정된 로직을 넣고, 파라미터로 받은 콜백을 실행해주자.
  • Callback 을 필드에 선언해두어도 되지만, 템플릿과 콜백간의 유연한 설계를 위해 파라미터로 넣는다.

 

TemplateCallbackTest

@Test
void callbackV2() {
    TimeLogTemplate template = new TimeLogTemplate();
    template.execute(() -> log.info("비즈니스 로직 1 실행"));
    template.execute(() -> log.info("비즈니스 로직 2 실행"));
}

 

템플릿 콜백 패턴 - 적용

로그 추적기 애플리케이션에 템플릿 콜백 패턴을 적용해보자.

 

TraceCallback 인터페이스

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

 

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();

          trace.end(status);
          return result;
      } catch (Exception e) {
          trace.exception(status, e);
          throw  e;
      }
  }
}
  • <T> 제네릭을 사용하여 타입 세이프티하게 템플릿을 만들자.
  • LogTrace 는 로그 시작과 끝, 예외 등을 기록하는 인터페이스로 템플릿 생성자에 파라미터로 넣어 생성하도록 하자.

 

image

  • Log 와 관련된 코드는 다음과 같이 구성되어 있다.

 

OrderControllerV5

image

  • 컨트롤러의 필드에 TraceTemplate을 추가하고, 템플릿에 LogTrace를 파라미터로 넣어주도록 변경하였다. ( TraceTemplate을 빈으로 등록하여 사용해도 된다.)
  • TraceTemplate 에는 콜백에 해당하는 call() 메서드를 구현하기 위해 람다를 사용하여 처리했다.

 

OrderServiceV5

image

  • 컨트롤러와 마찬가지로 TraceTemplate 적용을 위해 코드를 수정하자.

 

OrderRepositoryV5

image

  • 레포지토리도 동일하게 변경하자.

 

실행 결과 로그

2023-02-08 23:51:00.934  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] OrderController.request()
2023-02-08 23:51:00.939  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] |-->OrderService.request()
2023-02-08 23:51:00.939  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] |   |-->OrderRepository.save()
2023-02-08 23:51:01.950  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] |   |<--OrderRepository.save() time=1011ms
2023-02-08 23:51:01.951  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] |<--OrderService.request() time=1015ms
2023-02-08 23:51:01.951  INFO 1849 --- [nio-8080-exec-1] h.a.trace.logtrace.ThreadLocalLogTrace   : [cda5e30e] OrderController.request() time=1017ms
  • 기존에 사용하던 로그 추적기가 정상적으로 출력 된다.

 

정리

변하는 코드와 변하지 않는 코드를 분리하기 위해, 템플릿 콜백 패턴을 적용하였다.

하지만 최적화를 해도, 결국 원본 코드를 수정해야 된다는 한계가 있다.

개발자의 게으름에 대한 욕심을 바탕으로 프록시를 기반으로 원본 코드에 손대지 않고 적용하는 방법을 만들어냈다.

앞으로 XxxTemplate 을 본다면, 템플릿 콜백 패턴을 떠올리자.

 

 


본 포스팅은 인프런 - 스프링 핵심 원리 - 고급편을 참고하여 포스팅하였습니다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^