템플릿 메소드 패턴은 공통부분을 템플릿으로 만들어 부모 클래스에서 다루고 변경되는 부분을 자식 클래스에서 다루는 디자인 패턴이다.
따라서 상속에서 오는 단점을 그대로 가져오기 때문에 이런 단점을 해결한 전략 패턴을 도입할 예정이다.
템플릿 메소드 패턴과 전략 패턴은 GOF 디자인 패턴에 포함되지만 템플릿 콜백 패턴은 전략 패턴을 활용했을 뿐 GOF 패턴은 아니다.
단순히 전략 패턴에서 템플릿과 콜백 패턴이 강조되었고 스프링 내부에서 자주 사용되기 때문에 템플릿 콜백 패턴이라고 불려진다.
스프링에서는 JdbcTemplate , RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다. 스프링에서 이름에 XxxTemplate 가 있다면 템플릿 콜백 패턴으로 만들어져 있다 생각하면 된다.
콜백 패턴
앞서 언급했듯이 템플릿 메소드에서 상속에서 오는 단점을 해결하기 위한 패턴으로 상속이 아닌 위임을 활용하는 디자인 패턴이다.
빠르게 템플릿 메소드 패턴부터 살펴보자
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
call(); //상속
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void call();
}
execute()를 통해 공통 부분은 로그 추적 부분을 템플릿화 시키고 call()을 통해 자식클래스에서 오버라이딩 하여 변경되는 비즈니스 로직을 추가하여 사용하였다. 그렇기 때문에 상속의 단점을 가지고 온다.
그렇다면 전략 패턴은?
전략 패턴은 변하지 않는 부분을 Context 라는 곳에 두고, 변하는 부분을 Strategy 라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 해서 문제를 해결한다. 상속이 아니라 위임으로 문제를 해결하는 것이다. Context는 변하지 않는 템플릿 역할을 하고, Strategy는 변하는 알고리즘 역할을 한다.
public interface Callback {
void call();
}
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);
}
}
템플릿 메소드 패턴과 비교했을 때 call()을 호출하는 방법이 달라진 것을 볼 수 있다. 전략 패턴은 매개변수 인자를 통해 구현된 call()을 호출하고 있다.
템플릿 콜백 패턴
위처럼 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 콜백(callback)이라 한다.
쉽게 이야기해서 callback 은 코드가 호출( call )은 되는데 코드를 넘겨준 곳의 뒤( back )에서 실행된다는 뜻이다.
call()을 가지고 있는 Callback을 인자로 넘겨주고 TimeLogTemplate에서 실행하고 있는 바로 위 코드처럼 말이다.
해당 코드를 가지고 Contoller에 도입해보면 다음과 같이 실행할 수 있다.
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;
}
}
}
@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()", () -> {
orderService.orderItem(itemId);
return "ok";
});
}
}
Controller, Service, Repository 등 다양한 곳에서 사용되는 템플릿으로 반환값이 다 다를 수 있어 제네릭으로 템플릿을 만들어서 도입하였다.
해당 url로 접속하여 결과를 확인해보자.
원하던 대로 잘 동작하는 것을 확인할 수 있다.
이렇게 최적화를 많이 시켰음에도 불구하고 모든 Controller, Service, Repository에 코드를 수정하여 적용하는 것은 똑같다. 만약 클래스가 300개 라면 다음 처럼 모두 수정해주어야 한다. 매우 불편할 것이다.
따라서 템플릿 콜백 패턴 보다 프록시를 도입하여 기존 코드 변경없이 로그 추적기를 적용할 수 있는 방법을 살펴보자.
'Study > Design Pattern' 카테고리의 다른 글
데코레이터 패턴 - 로그 추적기 적용 (3) (1) | 2023.10.05 |
---|---|
템플릿 메소드 - 로그 추적기 적용 (1) (0) | 2023.10.02 |