전략 패턴을 학습할 때 보았던 것 처럼, 애플리케이션 의존 관계를 설정하듯 선 조립, 후 실행
을 원하는 것이 아니다. 변하는 부분과 변하지 않는 부분을 분리하는 것이 목표이고 이를 위해 코드를 실행할 때 변하지 않는 템플릿이 있고, 그 템플릿 안에서 원하는 부분만 살짝 다른 코드를 실행하고 싶을 뿐이다.
Context
는 변하지 않는 템플릿 역할을 한다. 변하는 부분은 파라미터로 넘어온 Strategy
의 코드를 실행해서 처리한다. 이렇게 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 콜백이라 한다.
📌 콜백?
프로그래밍에서 콜백(callback) 또는 콜애프터 함수(call-after function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.쉽게 말해, 코드가 호출(call)은 되는데 코드를 넘겨준 곳의 뒤(back)에서 실행된다는 뜻이다.
Context
가 템플릿 역할을 하고, Strategy
부분이 콜백으로 넘어온다 생각하면 된다.JdbcTemplate
, RestTemplate
, TransactionTemplate
, RedisTemplate
처럼 스프링에서 xxxTemplate
이름이 있다면 템플릿 콜백 패턴으로 만들어져 있다고 생각하면 된다.public interface TraceCallback<T> {
T call();
}
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;
}
}
}
템플릿 콜백 패턴을 적용한 OrderControllerV5
는 다음과 같다.
@RestController
pubilc 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";
}
});
}
}
콜백으로 익명 클래스를 전달하였는데, 람다를 사용하면 코드가 더 줄어들게 된다.
템플릿 메서드 패턴, 전략 패턴, 템플릿 콜백 패턴으로 변하는 코드와 변하지 않는 코드를 분리했고, 콜백에 람다까지 적용하여 코드를 최소화 하였다.
하지만, 결국 로그 추적기를 적용하기 위해 원본 코드를 수정해야 한다는 점이 한계점이다. 클래스가 늘어나면 늘어나는 만큼 전부 수정해야한다. 이를 해결하기 위한 방식은 프록시를 활용하는 것이다.