이전에 전략 패턴을 학습하면서 봤던 ContextV2 는 변하지 않는 템플릿 역할을 한다. 그리고 변하는 부분은 파라미터로 넘어온 Strategy 의 코드를 실행해서 처리한다. 이렇게 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 콜백(callback)이라 한다.
( 즉 ContextV2의 excute메서드의 인자로 넘어가는 Strategy가 콜백이다. )
콜백
프로그래밍에서 콜백(callback) 또는 콜애프터 함수(call-after function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다. (위키백과 참고)
( callback 은 코드가 호출( call )은 되는데 코드를 넘겨준 곳의 뒤( back )에서 실행된다는 뜻이다. )
자바 언어에서 콜백
자바 언어에서 실행 가능한 코드를 인수로 넘기려면 객체가 필요하다. 자바8부터는 람다를 사용할 수 있다.
자바 8 이전에는 보통 하나의 메소드를 가진 인터페이스를 구현하고, 주로 익명 내부 클래스를 사용했다. 최근에는 주로 람다를 사용한다.
스프링에서의 콜백 패턴
• 스프링에서는 ContextV2 와 같은 방식의 전략 패턴을 템플릿 콜백 패턴이라 한다. 전략 패턴에서 Context 가 템플릿 역할을 하고, Strategy 부분이 콜백으로 넘어온다 생각하면 된다.
• 참고로 템플릿 콜백 패턴은 GOF 패턴은 아니고, 스프링 내부에서 이런 방식을 자주 사용하기 때문에, 스프링 안에서만 이렇게 부른다. 전략 패턴에서 템플릿과 콜백 부분이 강조된 패턴이라 생각하면 된다.
• 스프링에서는 JdbcTemplate , RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다. 스프링에서 이름에 XxxTemplate 가 있다면 템플릿 콜백 패턴으로 만들어져 있다 생각하면 된다.
이제 템플릿 콜백 패턴을 주문 서비스에 적용해보자. 이름만 다르고 바뀐것은 없다.
Context --> Template
Strategy --> Callback
Template은 아래와 같다.
import hello.advanced.app.trace.TraceStatus;
import hello.advanced.app.trace.logtrace.LogTrace;
public class TraceTemplate<T> {
private final LogTrace trace;
public TraceTemplate(LogTrace trace) {
this.trace = trace;
}
public <T> T execute(TraceCallback<T> callback, String message) {
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;
}
}
}
Callback 인터페이스는 아래와 같다.
public interface TraceCallback<T> {
T call();
}
Controller
@RestController
public class OrderControllerV6 {
private final OrderServiceV6 orderService;
private final TraceTemplate<String> template;
private final LogTrace trace;
//@Autowired 생략가능
public OrderControllerV6(OrderServiceV6 orderService, LogTrace trace) {
this.orderService = orderService;
this.trace = trace;
this.template = new TraceTemplate<>(this.trace);/*생성자에서 Template 생성*/
}
@GetMapping("/v6/request")
public String request(String itemId) {
return template.execute(() -> {
orderService.orderItem(itemId);
return "ok";
}, "OrderControllerV.request()");
}
}
Service
@Service
public class OrderServiceV6 {
private final OrderRepositoryV6 orderRepository;
private final TraceTemplate<Void> template;
private final LogTrace trace;
public OrderServiceV6(OrderRepositoryV6 orderRepository, LogTrace trace) {
this.orderRepository = orderRepository;
this.trace = trace;
this.template = new TraceTemplate<>(this.trace);
}
public void orderItem(String itemId){
template.execute(() -> {
orderRepository.save(itemId);
return null;
}, "OrderService.orderItem");
}
}
Repository
@Repository
public class OrderRepositoryV6 {
private final LogTrace trace;
private final TraceTemplate<Void> template;
public OrderRepositoryV6(LogTrace trace) {
this.trace = trace;
this.template = new TraceTemplate<>(this.trace);
}
public void save(String itemId) {
template.execute(()-> {
saveDbLogic(itemId, 1000);
return null;
}, "OrderRepository.save()");
}
private void saveDbLogic(String itemId, int millis){
System.out.println(itemId+"을 저장");
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TraceTemplate은 자신의 Context를 실행하기 위해 LogTrace가 필요한데, 이를 생성자에서 빈 객체로 등록된 LogTrace를 받고있다.
이를 실행해보면 잘 동작하는 것을 확인할 수 있다.
OrderControllerV.request()
|-->OrderService.orderItem
| |-->OrderRepository.save()
테스트아이템을 저장
| |<--OrderRepository.save() time=1002ms
|<--OrderService.orderItem time=1004ms
OrderControllerV.request() time=1004ms
지금까지 적용한 패턴을 보면 변경에 열려있고, 확장에 닫혀있는 좋은 코드가 되었나 싶지만 어쨋든 로그 추적기 기능을 사용하기 위해서는
Controller, Service, Repository에 핵심 로직 외의 코드를 추가해야한다. 즉 현재는 주문 서비스 클래스가 3개 뿐이지만 주문 서비스를 위한 클래스가 수백개라면 수백개의 코드에 로그 추적을 위한 코드를 추가해주어야 하는 것이다.
하여 원본 코드를 수정하지 않고, 로그 추적기를 도입하는 방법이 있다. 이를 위해 다음 포스트에서는 프록시에 대해 학습해보자.
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어2 - 프록시 패턴과 데코레이터 패턴 (0) | 2022.01.06 |
---|---|
스프링 코어2 - 프록시 학습을 위한 프로젝트 만들기 (0) | 2022.01.06 |
스프링 코어2 -전략 패턴 (0) | 2022.01.05 |
스프링 코어2 - 템플릿 메서드 패턴(로그 추적기에 패턴 도입) (0) | 2022.01.04 |
스프링 코어2 - 로그추적기(feat.Thread Local) (0) | 2022.01.02 |