오봉이와 함께하는 개발 블로그

스프링 핵심 원리 - 고급편 > 템플릿 콜백 패턴 (적용, 정의 및 정리) 본문

BE/Spring

스프링 핵심 원리 - 고급편 > 템플릿 콜백 패턴 (적용, 정의 및 정리)

오봉봉이 2024. 5. 10. 00:21
728x90

템플릿 콜백 패턴 - 적용

  • TraceCallback 인터페이스
public interface TraceCallback<T> {
  T call();
}
  • TraceTemplate.java
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;
    }
  }
}
  • TraceTemplate은 템플릿 역할을 한다.
  • execute(..)를 보면 message 데이터와 콜백인 TraceCallback callback을 전달 받는다.
  • <T> 제네릭을 사용했다. 반환 타입을 정의한다.

V5

  • OrderControllerV5
@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(@RequestParam("itemId") String itemId) {
        return template.execute("OrderController.request()", new TraceCallback<>() {
            @Override
            public String call() {
                orderService.orderItem(itemId);
                return "ok";
            }
        });
    }
}
  • this.template = new TraceTemplate(trace) : trace 의존관계 주입을 받으면서 필요한 TraceTemplate 템플릿을 생성한다.
    • TraceTemplate을 처음부터 스프링 빈으로 등록하고 주입받아도 된다. (선택사항)
      • 스프링 빈 생성 시 new TraceTemplate()를 하기 때문에 TraceTemplate는 빈 생성 시 최초 한 번만 생성된다.
    • 위 방식의 장점은 테스트 시 LogTrace만 빈으로 생성하고 TrateTemplate은 빈으로 등록하지 않아도 자동으로 생성되기 때문에 의존성 주입이 보다 간단하다
      • @MockBean LogTrace logTrace
      • @MockBean TraceTemplate template
    • 매 Request마다 new TraceTemplate()을 통해 생성할 수도 있지만, 그렇게 되면 매 요청 시 생성하기 때문에 오버헤드 발생
  • OrderServiceV5
@Service
public class OrderServiceV5 {
    private final OrderRepositoryV5 orderRepository;
    private final TraceTemplate template;

    public OrderServiceV5(OrderRepositoryV5 orderRepository, LogTrace trace) {
        this.orderRepository = orderRepository;
        this.template = new TraceTemplate(trace);
    }

    public void orderItem(String itemId) {
        template.execute("OrderService.orderItem()", () -> {
            orderRepository.save(itemId);
            return null;
        });
    }
}
  • template.execute(.., new TraceCallback(){..}) : 템플릿을 실행하면서 콜백을 전달한다. 여기는 콜백으로 람다를 전달
  • OrderRepositoryV5
@Repository
public class OrderRepositoryV5 {
    private final TraceTemplate template;

    public OrderRepositoryV5(LogTrace trace) {
        this.template = new TraceTemplate(trace);
    }

    public void save(String itemId) {
        template.execute("OrderRepository.save()", () -> {
            //저장 로직
            if (itemId.equals("ex")) {
                throw new IllegalStateException("예외 발생!"); }
            sleep(1000);
            return null;
        });
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

로그

[aaaaaaaa] OrderController.request()
[aaaaaaaa] |-->OrderService.orderItem()
[aaaaaaaa] |   |-->OrderRepository.save()
[aaaaaaaa] |   |<--OrderRepository.save() time=1001ms
[aaaaaaaa] |<--OrderService.orderItem() time=1003ms
[aaaaaaaa] OrderController.request() time=1004ms

정리

템플릿 메서드 -> 전략 -> 템플릿 콜백 패턴까지 사용하면서 변하는 코드와 변하지 않는 코드를 분리하고, 더 적은 코드로 로그 추적기를 사용하기 위해 이런 저런 코드를 보았다.

그러나 지금까지의 방식으로는 로그 추적기 적용을 위해서는 모든 엔드포인트, 서비스, 리포지토리의 원본 코드를 수정해야 하는 문제가 있다.
클래스가 XXX개라면, 그 안에 메서드가 각 3개씩만 있어도 XXX*3개 모두 작업해야 한다.
템플릿 콜백 패턴까지 적용하면서 그나마 조금이라도 덜 힘들겠지만, 말 그대로 덜 힘든 것이다.

image


개발자는 게으른 개발자가 성공한다. 자동화와 딸깍이 습관화 되어야 한다..

다음엔 원본 코드를 수정하지 않고 로그 추적기 적용 방법을 알아보자.

 

출처: 김영한 지식공유자님의 스프링 핵심 원리 고급편

728x90
Comments