728x90
/**
* 전략을 파라미터로 전달받는 방식
*/
@Slf4j
public class ContextV2 {
public void execute(Strategy strategy) {
long startTime = System.currentTimeMillis();
strategy.call(); // 위임
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime = {}", resultTime);
}
}
/**
* 변하는 알고리즘
*/
public interface Strategy {
void call();
}
위의 ContextV2는 변하지 않는 템플릿 역할을 한다. 그리고 변하는 부분은 파라미터로 넘어온 Strategy의 코드를 실행해서 처리한다. 이렇게 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 callback이라고 한다.
ContextV2에서 콜백은 Strategy이다. 클라이언트가 직접 Strategy를 실행하는 것이 아니라, 클라이언트가 execute()를 실행할때 Strategy를 넘겨주고, ContextV2 뒤에서 Strategy가 실행된다.
이렇듯 템플릿 콜백 패턴은 GOF 패턴은 아니고, 전략 패턴에서 템플릿과 콜백 부분이 강조된 패턴인 스프링 안에서만 부르는 패턴이다. 즉, 스프링에서는 JdbcTemplate, RestTemplate, TransactionTemplate, RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다. 따라서 스프링에서 이름에 ~Template이 있다면 템플릿 콜백 패턴으로 만들어져있다고 생각하면 된다.
템플릿 콜백 패턴 적용
다음은 콜백을 전달하는 인터페이스이다.
public interface TraceCallback<T> {
T call();
}
다음은 템플릿 역할을 하는 클래스이다.
package hello.advanced.trace.callback;
import hello.advanced.trace.TraceStatus;
import hello.advanced.trace.logtrace.LogTrace;
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;
}
}
}
아래는 위 템플릿 콜백 패턴을 실제 애플리케이션에 적용한 예이다.
import hello.advanced.trace.logtrace.LogTrace;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService;
private final TraceTemplate template;
public OrderController(OrderService orderService, LogTrace trace) {
this.orderService = orderService;
this.template = new TraceTemplate(trace);
}
@GetMapping("/request")
public String request(@RequestParam String itemId) {
return template.execute("OrderController.request()", () -> {
orderService.orderItem(itemId);
return "ok";
});
}
}
이렇게 최종적으로 템플릿 콜백 패턴을 적용하고, 콜백으로 람다를 사용해서 코드 사용을 최소할 수 있다.
한계점
하지만 그럼에도 불구하고 로그 추적기를 적용하기 위해서는 원본 코드를 수정해야 한다. 이 한계점을 넘어서기 위해서 원본 코드에 손대지 않고, 적용할 수 있도록 하려면 프록시 개념을 이해해야 한다.
728x90