지난 시간에 이어서 결국 LogTrace클래스를 완성하였지만, 이를 Controller, Service, Repository에 적용함에 있어 문제가 생겼다.
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
private final LogTrace trace;
@GetMapping("/request")
public String request(String itemId) {
TraceStatus status = null;
try {
status = trace.begin("OrderController.request()");
//관심사항
orderService.orderItem(itemId);
trace.end(status);
return "ok";
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
}
위 코드를 보면, Controller에 LogTrace와 관련된 내용까지 적혀있는 것을 볼 수 있다. 정작 컨트롤러에서 관심 있는 코드는
orderService.orderItem(itemId);
이 한 줄 뿐이다. 이는 관심사의 분리가 되지 못한 패턴이며, 만약에 클래스가 100개, 1000개로 늘어나면 변경에 있어서도 큰 제약이 따르게 된다.
스프링에서는 이를 템플릿-콜백 패턴으로 해결한다.
템플릿-콜백 패턴을 쉽게 설명하면, 콜백은 실행 가능한 함수 조각이며, 클라이언트는 변경되는 부분만을 콜백으로 전달하여, 템플릿에게 실행하도록 위임하는 것이다. 이를 통해 클라이언트는 템플릿의 코드 구조를 알 필요가 없고, 관심사의 분리가 가능해진다.
이 패턴을 우리의 코드에 적용시켜보자.
public interface TraceCallback<T> {
T call();
}
@RequiredArgsConstructor
public class TraceTemplate {
private final LogTrace 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;
}
}
}
이 코드가 템플릿-콜백 부분이다. 위에서 본 Controller와 많이 닮아있음을 알 수 있다. 주요 관심사인 로그부분과 그 중간에 위치한 콜백 실행 부분이 있다. excute함수는 파라미터로 받은 콜백을 로그부분과 함께 실행시켜주는 것이다.
이러면 Controller는 어떻게 바뀔 수 있을까?
@RestController
public class OrderController {
private final OrderService orderService;
private final TraceTemplate template;
public OrderController(OrderService orderService, LogTrace logTrace) {
this.orderService = orderService;
this.template = new TraceTemplate(logTrace);
}
@GetMapping("/request")
public String request(String itemId) {
return template.execute("OrderController.request()", () -> {
orderService.orderItem(itemId);
return "ok";
});
}
}
LogTrace를 주입받고, 이를 통해 TraceTemplate을 초기화한다. 그러면 request함수에서는 TraceTemplate.excute() 함수와 함께 콜백만 전달해주면 되는 것이다.
template.execute("OrderController.request()", () -> {
orderService.orderItem(itemId);
return "ok";
});
이렇게 람다표현식으로 코드조각을 전달해주면 나머지 일은 TraceTemplate이 실행시켜주게 된다.
우리는 이 템플릿-콜백 패턴을 통해 관심사의 분리를 해냈다. 이번 시간을 통해 관심사의 분리에 대해 좀 더 깊게 생각할 수 있게 되었다.