Template Method 패턴
자주 변경되는 점과 변경되지 않는 지점을 분리하여 설계한 방법이다. 변경되지 않는 점은 Template 양식에 위치하고, 자주 변경되는 지점은 사용할 때마다 구현해서 사용하는 구현체로 남겨둔다.
단일 책임 원칙(SRP)을 지킬 수 있다. 모듈화를 함으로써 로깅과 비지니스 로직을 분리했다. 그리고 공통되는 로깅 로직을 따로 빼서 공통으로 코드를 관리하는 덕분에 변경이 일어날 시 한 곳만 수정을 하면 된다.
GOF 디자인 패턴 책에서의 정의는 다음과 같다
템플릿 페머스 디자인 패턴의 목적은 다음과 같습니다.
"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단게를 재정의할 수 있습니다"
단점으로는 2가지가 있다.
첫째, 예시에서 보듯이 Template을 상속해서 사용하기 때문에 자식에서는 불필요한 코드들을 가지게 된다는 점이 있다.
AbstractTemplate<String> template = new AbstractTemplate<>(logTrace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "OK";
}
};
둘째, 상속관계 때문에 자식과 부모로 강하게 의존하고 있다는 점이다. 로깅이라는 기능과 주문 서비스의 기능은 그렇게 큰 연관관계가 없는데 강한 의존관계를 가지게 되면, 로깅이라는 기능에 변경이 일어났을 때 서비스 비지니스 코드들에도 변경 영향이 생기기 때문이다. 다형성을 위반할 가능성이 있다.
그래서 전략패턴 Strategy Pattern을 사용한다.
기존 코드
@RestController
@RequiredArgsConstructor
public class OrderControllerV1 {
private final OrderServiceV1 orderService;
private final LogTrace trace;
@GetMapping("/v1/request")
public String request(String itemId) {
TraceStatus status = null;
try {
status = trace.begin("OrderController.request()");
orderService.orderItem(status.getTraceId(),itemId);
trace.end(status);
return "ok";
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
}
@Service
@RequiredArgsConstructor
public class OrderServiceV1 {
private final OrderRepositoryV1 orderRepositoryV0;
private final HelloTraceV1 trace;
public void orderItem(TraceId traceId, String itemId){
TraceStatus status = null;
try {
status = trace.beginSync(traceId, "OrderService.request()");
orderRepositoryV0.save(status.getTraceId(),itemId);
trace.end(status);
}catch (Exception e){
trace.exception(status, e);
throw e;
}
}
}
@Repository
@RequiredArgsConstructor
public class OrderRepositoryV1 {
private final HelloTraceV1 trace;
public void save(TraceId traceId, String itemId) {
TraceStatus status = null;
try {
status = trace.beginSync(traceId, "OrderRepository.save()");
//저장 로직
if (itemId.equals("ex")) {
throw new IllegalStateException("예외 발생!");
}
sleep(1000);
trace.end(status);
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
private void sleep(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class LogTraceConfig {
@Bean
private LogTrace logTrace(){
return new ThreadLocalLogTrace();
}
}
TemplateMethod 패턴 적용 코드
@RestController
@RequiredArgsConstructor
public class OrderControllerV2 {
private final OrderServiceV2 orderService;
private final LogTrace logTrace;
@GetMapping("/v2/request")
public String request(String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<>(logTrace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "OK";
}
};
return template.execute("OrderController.request()");
}
}
@Service
@RequiredArgsConstructor
public class OrderServiceV2 {
private final OrderRepositoryV2 orderRepositoryV2;
private final LogTrace logTrace;
public void orderItem(String itemId){
AbstractTemplate<Void> abstractTemplate = new AbstractTemplate<>(logTrace) {
@Override
protected Void call() {
orderRepositoryV2.save(itemId);
return null;
}
};
abstractTemplate.execute("OrderService.request()");
}
}
@Repository
@RequiredArgsConstructor
public class OrderRepositoryV2 {
private final LogTrace logTrace;
public void save(String itemId) {
AbstractTemplate<Void> abstractTemplate =new AbstractTemplate<>(logTrace) {
@Override
protected Void call() {
//저장 로직
if (itemId.equals("ex")) {
throw new IllegalStateException("예외 발생!");
}
sleep(1000);
return null;
}
};
abstractTemplate.execute("OrderRepository.save()");
}
private void sleep(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class OrderTest {
private OrderServiceV2 orderService;
private ThreadLocalLogTrace logTrace;
@BeforeEach
void setup(){
this.logTrace = new ThreadLocalLogTrace();
OrderRepositoryV2 orderRepository = new OrderRepositoryV2(logTrace);
this.orderService = new OrderServiceV2(orderRepository, logTrace);
}
@Test
void test1(){
// given
OrderControllerV2 controllerV2 = new OrderControllerV2(orderService, logTrace);
// when
String result = controllerV2.request("pizza1");
// then
assertThat(result).isEqualTo("ok");
}
}
00:51:31.097 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] OrderController.request()
00:51:31.106 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] |-->OrderService.request()
00:51:31.106 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] | |-->OrderRepository.save()
00:51:32.118 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] | |<--OrderRepository.save() time=1012ms
00:51:32.118 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] |<--OrderService.request() time=1017ms
00:51:32.118 [Test worker] INFO hello.advanced.trace.logtrace.ThreadLocalLogTrace - [06093d27] OrderController.request() time=1022ms
'공부노트 > 스프링' 카테고리의 다른 글
Spring AOP 적용 (0) | 2022.06.25 |
---|---|
스프링 AOP (0) | 2022.06.19 |
프록시 패턴과 데코레이션 패턴 (0) | 2022.04.30 |
Strategy Pattern, Template Callback 패턴 (0) | 2022.04.24 |
스프링 프레임워크 - 기초 (0) | 2022.04.10 |