[Spring] ๋์์ธ ํจํด - Template Method ํจํด
by rlaehddnd0422๋ก๊ทธ ์ถ์ ๊ธฐ์ ThreadLocal์ ๋์ ํด์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ธฐ๋ ๋ถํธํจ์ ์ ๊ฑฐํ์ต๋๋ค. ์ด๋ฒ์๋ ๋ก๊ทธ ์ถ์ ๊ธฐ์ ์ฌ๋ฌ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ ์ฉ์์ผ ๋น์ฆ๋์ค ๋ก์ง์์ ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ์ธ ๋ก๊ทธ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํด๋ด ์๋ค.
"ํต์ฌ ๊ธฐ๋ฅ"
- ํด๋น ๊ฐ์ฒด๊ฐ ์ ๊ณตํ๋ ๊ณ ์ ์ ๊ธฐ๋ฅ์ ๋งํฉ๋๋ค.
- ์๋ฅผ ๋ค๋ฉด OrderService์ orderItem()์ ํต์ฌ ๊ธฐ๋ฅ์ ์ฃผ๋ฌธ ๋ก์ง์ ๋๋ค.
- OrderService์ orderItem()์ ์ฃผ๋ฌธ ๋ก์ง : orderRepository์ save()๋ฅผ ํธ์ถํ์ฌ ์ฃผ๋ฌธ์ ์ ์ฅ
"๋ถ๊ฐ ๊ธฐ๋ฅ"
- ํต์ฌ ๊ธฐ๋ฅ์ ๋ณด์กฐํ๊ธฐ ์ํด ์ ๊ณต๋๋ ๊ธฐ๋ฅ
- ์๋ฅผ ๋ค๋ฉด OrderService์ orderItem()์ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ก๊ทธ ์ถ์ ๋ก์ง, ํธ๋์ญ์ ๊ธฐ๋ฅ
์๋ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๋ถ๊ฐ ๊ธฐ๋ฅ๊ณผ ํต์ฌ ๊ธฐ๋ฅ์ด ํ๋์ ๋ฉ์๋์ ๋ค์์ฌ ์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ์ด ์ฝ๋๋ฅผ ์ข์ ์ฝ๋๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค. ๋ก์ง์ด ๋จ์ํด์ ์ด์ ๋์ง ๋ก์ง์ด ๊ฝค๋ ๋ณต์กํด์ง ๊ฒฝ์ฐ ํต์ฌ๊ธฐ๋ฅ์ด ์ด๋ค ์ฝ๋์ด๊ณ ๋ถ๊ฐ๊ธฐ๋ฅ์ด ์ด๋ค ์ฝ๋์ธ์ง ๊ฐ๋ ํ๊ธฐ ๊ต์ฅํ ์ด๋ ค์์ง ์ ์์ต๋๋ค.
ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ํ ๊ตฐ๋ฐ์ ๋ชจ์์ ์์ฑํ๊ธฐ ๋ณด๋ค, ๋ถ๋ฆฌํ์ฌ ์์ฑํ๋ ๊ฒ์ด ์ข์ ์ค๊ณ๋ผ๊ณ ํ ์ ์์ต๋๋ค.
public void orderItem(TraceId traceId, String itemId) {
// ๋ถ๊ฐ๊ธฐ๋ฅ
TraceStatus status = trace.begin("OrderService.orderItem()");
try {
orderRepository.save(itemId); // ํต์ฌ ๊ธฐ๋ฅ
trace.end(status); // ๋ถ๊ฐ ๊ธฐ๋ฅ
} catch (Exception e) {
trace.exception(status, e); // ๋ถ๊ฐ ๊ธฐ๋ฅ
throw e;
}
}
์ด์ 1) ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ด ๋ฌด์์ธ์ง ์์๋ณด๊ณ , 2) ์์ ์ฝ๋๋ก ์ ์ฉํด๋ณธ ํ, 3) ๊ธฐ์กด ๋ก๊ทธ ์ถ์ ๊ธฐ์ ์ ์ฉ์์ผ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด(Template Method Pattern)
์ ์ : ์ด๋ค ์์ ์ ์ฒ๋ฆฌํ ๋ ์ผ๋ถ๋ถ(๋ถ๊ฐ ๊ธฐ๋ฅ)์ ์๋ธ ํด๋์ค๋ก ์บก์ํํด์ ์ ์ฒด ์ผ์ ์ํํ๋ ๊ตฌ์กฐ๋ ๋ฐ๊พธ์ง ์์ผ๋ฉด์ ํน์ ๋จ๊ณ์์ ์ํํ๋ ๋ด์ญ(ํต์ฌ ๊ธฐ๋ฅ)์ ๋ฐ๊พธ๋ ํจํด์ ๋๋ค.
- ๋ค์ ๋งํด ์ ์ฒด์ ์ผ๋ก ๊ตฌ์กฐ๋ ๋์ผํ๋, ๋ถ๋ถ์ ์ผ๋ก ๋ค๋ฅธ ๊ตฌ๋ฌธ์ผ๋ก ๊ตฌ์ฑ๋ ๋ฉ์๋์ ์ฝ๋ ์ค๋ณต์ ์ต์ํํ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ ํจํด์ ๋๋ค.
- ์์ ๊ฐ๋ฐํ ๋ก๊ทธ ์ถ์ ๊ธฐ๋ก ๋์ ํด๋ณด๋ฉด Controller๊ณ์ธต, Service ๊ณ์ธต, Repository ๊ณ์ธต์์ ๋ก๊ทธ ์ถ์ ๊ธฐ๋ฅผ ์ ์ฉ์ํด์ผ๋ก์จ ๋ชจ๋ ๊ณ์ธต์ ๊ฐ์ ์ฝ๋๋ฅผ ์ค๋ณต์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฌธ์ ๊ฐ ์์๋๋ฐ, ์ด ๋ถ๋ถ๋ค์ ์ถ์ ํด๋์ค๋ก ์์ฑํด์ ๋ถ๊ฐ ๊ธฐ๋ฅ๋ค์ ์ถ์ ํด๋์ค์๋ ๊ตฌํํ๊ณ , ํต์ฌ ๊ธฐ๋ฅ๋ค์ ์ถ์ ํด๋์ค์ ์ ์ธ๋ง ํด๋์ ํ, ์ด ์ถ์ํด๋์ค๋ฅผ ์์ ๋ฐ์ ๊ฐ ๊ณ์ธต์ ์ ์ฉํ๋ค๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด : ๋ถ๋ชจ ํด๋์ค์ ์๊ณ ๋ฆฌ์ฆ์ ๊ณจ๊ฒฉ์ธ ํ ํ๋ฆฟ์ ์ ์ํ ํ,
์ผ๋ถ ๋ณ๊ฒฝ๋๋ ๋ก์ง์ ์์ ํด๋์ค์ ์ ์ํ๋ ๊ฒ.
์ค๋ช ์ผ๋ก ์ดํดํ๋ ๊ฒ๋ณด๋ค ์ฝ๋๋ก ๋ณด๋ฉด ๋ ์ฝ๊ฒ ์ดํดํ ์ ์์ต๋๋ค. ์์ ์ฝ๋๋ก ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด ์์ ์ฝ๋
@Slf4j
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
// ๋น์ฆ๋์ค ๋ก์ง ์คํ
call();
// ๋น์ฆ๋์ค ๋ก์ง ์ข
๋ฃ
long endTime = System.currentTimeMillis();
log.info("result Time = {}", endTime - startTime);
}
protected abstract void call();
}
- ์ด ์์ ์ฝ๋์์๋ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ฝ๋ ์คํ ์๊ฐ ์ถ๋ ฅ์ ๋๋ค.
- execute() ๋ฉ์๋๋ฅผ ๋ณด๋ฉด ํต์ฌ ๊ธฐ๋ฅ์ call()๋ก ํธ์ถํ๊ณ , ๋๋จธ์ง ๊ณตํต ๋ถ๊ฐ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํ์ ํด๋์์ผ๋ก์จ ์ ์ธํ์ฌ ํต์ฌ ๊ธฐ๋ฅ์ ์์๋ฐ์ ํด๋์ค์์ ๊ตฌํํ์ฌ ์ฌ์ฉํ ์ ์๊ฒ ๊ตฌ์ฑํ์์ต๋๋ค.
ํต์ฌ ๊ธฐ๋ฅ์ ํ ํ๋ฆฟ์ ์์๋ฐ์ ๊ตฌํ
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ");
}
}
- ์ค๋ณต ์ฝ๋์ธ ๋ถ๊ฐ ๊ธฐ๋ฅ ์ฝ๋๋ ์ง์ ์ ์ธํ๊ณ , ์ค๋ณต๋์ง ์๋ ๊ฐ๊ฐ์ ๋ฉ์๋์ ๊ณ ์ ๊ธฐ๋ฅ์ ํ ํ๋ฆฟ์ ์์๋ฐ์ ์ง์ ๊ตฌํํ์ฌ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ์ฝ๋์ ์ค๋ณต์ ์ ๊ฑฐํ์๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
ํ ์คํธ ์ฝ๋
/**
* ํ
ํ๋ฆฟ ๋ฉ์๋ ํจํด ์ ์ฉ
*/
@Test
public void templateMethodV1() throws Exception {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
}
- + ๋ฒ์ธ) ์์๊ด๊ณ์ธ ๋ ํด๋์ค๊ฐ ์์๋ ์์ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์, ์ ์ธ๋ถ์๋ ๋ถ๋ชจ ํด๋์ค๋ก ์์ฑํ์.
- ๋คํ์ฑ์ ์ ์ฉํ์ฌ ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ฉฐ, ๋์ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ ์ ์๊ฒ ํ๊ธฐ ์ํด.
Tip : ์ต๋ช ๋ด๋ถ ํด๋์ค๋ก ๊ณผ๋ํ ํด๋์ค ์์ฑ ๋ณด์
@Slf4j public class SubClassLogic1 extends AbstractTemplate { @Override protected void call() { log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ"); } }โโ
@Slf4j public class SubClassLogic2 extends AbstractTemplate { @Override protected void call() { log.info("๋น์ฆ๋์ค ๋ก์ง2 ์คํ"); } }โ
์์ ๊ฐ์ด ๊ธฐ๋ฅ๋ค์ ๊ตฌํํ ๋๋ง๋ค ํด๋์ค๋ฅผ ์์ฑํ๋ ๊ฒ ๋ณด๋ค, ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ์๋ ์ต๋ช ๋ด๋ถ ํด๋์ค๋ก ์ผํ์ฑ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค. ์ด ๋์๋ ๋น์ฐํ ์๊ธฐ์ง๋ง, ์ต๋ช ๋ด๋ถ ํด๋์ค์ด๋ฏ๋ก AbstractTemplate template1 = new AbstractTemplate() ์ ๊ฐ์ด ํ์ ๋ถ๋ชจ ํด๋์ค๋ก ์ง์ ํด์ค๋๋ค.
์ต๋ช ๋ด๋ถ ํด๋์ค ์ ์ฉ@Test public void templateMethodV2() throws Exception { AbstractTemplate template1 = new AbstractTemplate() { @Override protected void call() { log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ"); } }; template1.execute(); AbstractTemplate template2 = new AbstractTemplate() { @Override protected void call() { log.info("๋น์ฆ๋์ค ๋ก์ง2 ์คํ"); } }; template2.execute(); }
๋ก๊ทธ ์ถ์ ๊ธฐ์ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ ์ฉ
์์ ์ฝ๋๋ฅผ ํตํด ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ด๋ป๊ฒ ์ ์ฉํด์ผ ํ๋์ง ๊ฐ์ด ์ค๋๋ฐ์. ๋ง์ง๋ง์ผ๋ก ์์ ๊ฐ๋ฐํ ๋ก๊ทธ ์ถ์ ๊ธฐ์ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ ์ฉ์์ผ ํต์ฌ ๊ธฐ๋ฅ๊ณผ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ถ๋ฆฌํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractTemplate<T> {
private final LogTrace trace;
public T execute(String msg) {
TraceStatus status = null;
try {
status = trace.begin(msg);
// ๋ก์ง ํธ์ถ
T result = call();
trace.end(status);
return result;
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
protected abstract T call();
}
- ๊ณ์ธต ๋ง๋ค ๋ก์ง์ ๋ฐ๋ผ ๋ฆฌํด ํ์
์ด ๋ฌ๋ผ์ง ์ ์๊ธฐ ๋๋ฌธ์ ์ ๋๋ฆญ์ผ๋ก ๋ชจ๋ ํ์
์ด ๋ค์ด์ฌ ์ ์๊ฒ ์ด์ด๋์์ต๋๋ค.
- protected abstract T call() : Controller์์๋ ํต์ฌ๊ธฐ๋ฅ์ ๋ฆฌํดํ์ ์ด String์ด๊ณ , ๋๋จธ์ง ๊ณ์ธต์์๋ ํต์ฌ๊ธฐ๋ฅ์ ๋ฆฌํดํ์ ์ด void์ด๋ฏ๋ก ํต์ฌ ๊ธฐ๋ฅ ํธ์ถ ๋ฉ์๋ call() ๋ํ ๋ฆฌํด ํ์ ์ T๋ก ์ด์ด๋
Controller
@GetMapping("/v4/request")
public String request(@RequestParam String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<String>(trace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "ok";
};
};
return template.execute("OrderController.request()");
}
์์ ๊ด๊ณ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ต๋ช ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ํต์ฌ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ํด๋์ค๋ฅผ ์์ฑํ์ง ์๊ณ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ ์ฉํ์์ต๋๋ค.
- Controller ๊ณ์ธต์์๋ ๋ฆฌํดํ์ ์ด String ์ด๋ฏ๋ก ํ ํ๋ฆฟ ํด๋์ค ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ํ์ ์ String์ผ๋ก ์ง์
Service
public void orderItem(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepository.save(itemId);
return null;
}
};
template.execute("OrderService.orderItem()");
}
- Service๊ณ์ธต์์์ ํต์ฌ๋ก์ง์ ๋ฆฌํดํ์ ์ void์ด๋ฏ๋ก ํ ํ๋ฆฟ ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ํ์ ์ Void๋ก ์ง์
Repository
public void save(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<Void>(trace) {
@Override
protected Void call() {
if (itemId.equals("ex")) {
throw new IllegalStateException("์์ธ ๋ฐ์");
}
sleep(1000);
return null;
}
};
template.execute("OrderRepository.save()");
}
- Repository ๊ณ์ธต ๋ํ ๋ฆฌํด ํ์ ์ด ์์ผ๋ฏ๋ก ํ ํ๋ฆฟ ๊ฐ์ฒด ์์ฑ ์ ํ์ ์ Void๋ก ์ง์ ํ์์ต๋๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋์ ๋จ์
- ์์์ ์ฌ์ฉํ๋ฏ๋ก ์์์์ ์ค๋ ๋จ์ ๋ค์ด ๋ฐ๋ผ์ต๋๋ค.
- ์์ ํด๋์ค๊ฐ ๋ถ๋ชจ ํด๋์ค์ ์ปดํ์ผ ์์ ์์ ๊ฐํ๊ฒ ๊ฒฐํฉ๋ฉ๋๋ค.
- ์ด๊ฒ์ ๋๋ ค๋งํ๋ฉด ์์ ํด๋์ค ์
์ฅ์์๋ ๋ถ๋ชจ ํด๋์ค์ ๊ธฐ๋ฅ์ ์ ํ ์ฌ์ฉํ์ง ์์ง๋ง, ๋ถ๋ชจ ํด๋์ค๋ฅผ ์์์ผ ํ๋ค๋ ๊ฒ์
๋๋ค.
- ์์ ํด๋์ค์์ ๋ถ๋ชจ ํด๋์ค์ execute() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋๊ฐ ? X
- ์ด๊ฒ๋ ๋ฐ์ง๊ณ ๋ณด๋ฉด ์ข์ ์ค๊ณ๋ ์๋๋๋ค.
- ๊ทธ๋ฆฌ๊ณ ํจํด ์ ์ฉ์ ์ํด ๋ณ๋์ ํด๋์ค๋ ์ต๋ช ๋ด๋ถ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ผ ํ๋ ๋ถ๋ถ๋ ๊น๋ํ์ง ์์ต๋๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด๊ณผ ๋น์ทํ ์ญํ ์ ํ๋ฉด์ ์์์ ๋จ์ ์ ์ ๊ฑฐํ ์ ์๋ ๋์์ธ ํจํด์ด ๋ฐ๋ก ์ ๋ต ํจํด (Strategy Pattern)์ธ๋ฐ, ์ด ๋ค์ ๊ธ์์ ์ ๋ต ํจํด์ ๋ํด ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
'๐ Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422