-
Spring Core - 템플릿 콜백 패턴(시작, 적용, 정리)FrameWork/Spring&Spring-boot 2024. 3. 7. 13:41
템플릿 콜백 패턴 - 시작
ContextV2는 변하지 않는 템플릿 역할을 한다. 그리고 변하는 부분은 파라미터로 넘어온 Strategy의 코드를 실행해서 처리한다. 이렇게 다른 코드의 파라미터로서 넘겨주는 실행 가능한 코드를 콜백(callback)이라 한다.
💡tip) 콜백 정의
프로그래밍에서 콜백(callback)또는 콜애프터 함수(call-after-function)는 다른 코드의 파라미터로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행 할 수도 있다.쉽게 이야기해서 callback은 코드가 호출(call)은 되는데 코드를 넘겨준 곳의 뒤(back)에서 실행 된다는 뜻이다.
- ContextV2 예제에서 콜백은 Strategy이다.
- 여기에서는 클라이언트에서 직접 Strategy를 실행하는 것이 아니라 클라이언트(람다를 정의해서 넘겨준 클래스)가 ContextV2.execute()를 실행할 때 Strategy를 넘겨주고, ContextV2 뒤에서 Strategy가 실행된다.
자바 언어에서 콜백
- 자바 언어에서 실행 가능한 코드를 인수로 넘기려면 객체가 필요하다. 자바 8부터는 람다를 사용할 수 있다.
- 자바8 이전에는 보통 하나의 메소드를 가진 인터페이스를 구현한고, 주로 익명 내부 클래스를 사용했다.
- 최근에는 주로 람다를 사용한다.
템플릿 콜백 패턴
- 스프링에서는 ContextV2와 같은 방식의 전략 패턴을 템플릿 콜백 패턴이라고 한다. 전략 패턴에서 Context가 템플릿 역할을 하고 Strategy 부분이 콜백으로 넘어온다고 생각하면 된다.
- 참고로 템플릿 콜백 패턴은 GOF패턴은 아니고, 스프링 내부에서 이런 방식을 자주 사용하기 때문에, 스프링 안에서만 이렇게 부른다. 전략 패턴에서 템플릿과 콜백 부분이 강조된 패턴이라고 생각하면 된다.
- 스프링에서 JDBCTemplate, RestTemplate, TransactionTemplate, RedisTemplate 처럼 다양한 템플릿 콜백 패턴이사용된다. 스프링에서 이름에 XxxTemplate가 있다면 템플릿 콜백 패턴으로 만들어져 있다고 생각하면 된다.
템플릿 콜백 패턴 - 예제
템플릿 콜백 패턴을 구현해보자 ContextV2와 내용이 같고 이름만 다르므로 크게 여려움은 없을 것이다.
- Context → Template
- Strategy → Callback
CallBack-인터페이스
package com.spring.core.trace.strategy.code.strategy.template; public interface Callback { void call(); }
콜백 로직을 전달할 인터페이스이다.
TimeLogTemplate
import com.spring.core.trace.strategy.code.tempalte.Callback; import lombok.extern.slf4j.Slf4j; @Slf4j 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); } }
TemplateCallbackTest
@Slf4j public class TemplateCallbackTest { /** * 템플릿 콜백 패턴 - 익명 내부 클래스 */ @Test void callbackV1(){ TimeLogTemplate template = new TimeLogTemplate(); template.execute(new Callback() { @Override public void call() { log.info("비지니스 로직1 실행"); } }); template.execute(new Callback() { @Override public void call() { log.info("비지니스 로직2 실행"); } }); } /** * 템플릿 콜백 패턴 - 람다 */ @Test void callbackV2(){ TimeLogTemplate template = new TimeLogTemplate(); template.execute(()->log.info("비지니스 로직1 실행")); template.execute(()->log.info("비지니스 로직2 실행")); } }
별도의 클래스를 만들어서 전달해도 되지만 콜백을 사용할 경우 익명 내부 클래스나 람다를 사용하는 것이 편리하다.
물론 여러곳에서 함께 사용되는경우 재사용을 위해 콜백을 별도의 클래스로 만들어도된다.
템플릿 콜백 패턴 - 적용
TraceCallback - 인터페이스
package com.spring.core.trace.strategy.code.tempalte; public interface TraceCallback<T> { T call(); }
- 콜백을 전달하는 인터페이스이다.
- <T> 제네릭을 사용했다. 콜백의 반환 타입을 정의한다.
TraceTemplate
package com.spring.core.trace.callback; import com.spring.core.logTrace.LogTrace; import com.spring.core.trace.TraceStatus; public class TraceTemplate { 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; } } }
- TraceTempalte는 템플릿 역할을한다.
- execute()를 보면 message 데이터와 콜백인 TraceCallback callback을 전달 받는다.
- <T> 제네릭을 사용했다. 반환 타입을 정의한다.
OrderControllerV5
package com.spring.core.app.v5; import com.spring.core.logTrace.LogTrace; import com.spring.core.trace.callback.TraceTemplate; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderControllerV5 { private final OrderServiceV5 orderService; private final TraceTemplate traceTemplate; public OrderControllerV5(OrderServiceV5 orderService, LogTrace trace) { this.orderService = orderService; this.traceTemplate = new TraceTemplate(trace); } @GetMapping("/v5/order") public String createOrder(String itemId){ return traceTemplate.execute("OrderController.createOrder()",()-> { orderService.orderItem(itemId); return "ok"; }); } }
- this.template = new TraceTemplate(trace) : trace 의존관계 주입 받으면서 필요한 TraceTemplate 템플릿을 생성한다. 참고로 TraceTemplate를 처음부터 스프링 빈으로 등록하고 주입받아도 된다. 이부분은 선택이다.
정리
지금까지 변하는 코드와 변하지 않는 코드를 분리하고, 여러가지 다양한 방식으로 로그적기를 구현했다. 템플릿메서드 패터, 전략 패턴, 그리고 템플릿 콜백 패턴까지 지내아면서 변하는 코드와 변하지 않는 코드를 분리했다. 그리고 최종적으로 템플릿 콜백 패턴을 적용하고 콜백으로 라다를 사요애서 코드 사용도 최소화 할 수 있었다.
한계
그런데 지금까지 설명한 방식의 한계는 아무리 최적화를 해도 결국 로그 추적기를 적용하기 위해서는 원본 코드를 수정해야 한다. 클래스가 수백개이면 수백개를 더 힘들게 수정하는가 조금 덜 힘들게 수정하는가의 차이가 있을 뿐, 본질적으로 코드를 다 수정해야 하는 것은 마찬가지다.
❓ 수 많은 개발자가 이 문제에 대해 집요하게 고민해 왔고, 여러가지 방향으로 해결책을 만들어 왔다. 지금 부터 원본 코드를 손대지 않고 로그 추적기를 사용할 수 있는 방법을 알아보자 → 그러기 위해서는 프록시 개념을 먼저 이해해야 한다.
[출처 - 스프링 핵심원리-고급편, 저 김영한]
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring Core - 프록시 패턴, 데코레이터 패턴 - 소개(요구사항 추가) (0) 2024.03.08 Spring Core - 프록시 패턴(예제 프로젝트) (0) 2024.03.08 Spring Core - 전략 패턴(시작, 예제1, 예제2) (1) 2024.03.07 Spring Core - 템플릿 메서드 패턴(예제3, 적용1, 적용2) (0) 2024.03.06 Spring Core - 템플릿 메서드 패턴(시작, 예제1, 예제2, 정의) (1) 2024.03.06