๋ก๊ทธ ์ถ์ ๊ธฐ๋ก ์์๋ณด๋ ๋์์ธํจํด
by JiwonDev
์คํ๋ง ํต์ฌ ์๋ฆฌ - ๊ณ ๊ธํธ - ์ธํ๋ฐ | ๊ฐ์
์คํ๋ง์ ํต์ฌ ์๋ฆฌ์ ๊ณ ๊ธ ๊ธฐ์ ๋ค์ ๊น์ด์๊ฒ ํ์ตํ๊ณ , ์คํ๋ง์ ์์ ์๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค., - ๊ฐ์ ์๊ฐ | ์ธํ๋ฐ...
www.inflearn.com
์๋น์ค๋ฅผ ํ์ฅํ๊ณ ์ง์๊ฐ๋ฅํ ์ด์์ ํ๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ ์ ๋ณด๊ฐ ํ์ํ๋ค.
- ์ด๋ ์์ ์์ ์ฑ๋ฅ์ ๋ณ๋ชฉ์ด ๋ฐ์ํ๊ณ ์๋๊ฐ?
- ์ด๋ค ์ฅ์ ๊ฐ ๋ฐ์ํ์ผ๋ฉฐ, ๊ณ ๊ฐ์ด ๊ฐ์ฅ ๋ง์ด ๊ฒช๋ ์ฅ์ ๋ ๋ฌด์์ธ๊ฐ?
- ๊ฐ์ฅ ํธ๋ํฝ์ด ๋ง์ ์๋น์ค / ์ ์ฌ์ฉ๋์ง ์๋ ์๋น์ค ๊ธฐ๋ฅ์ ๋ฌด์์ธ๊ฐ?
์ด๋ฌํ ๋ก๊ทธ๋ค์ ์ด๋ป๊ฒ ๋จ๊ธฐ๊ณ ๊ด๋ฆฌํด์ผํ๋์ง ์ฐจ๊ทผ์ฐจ๊ทผ ์์๋ณด์.
๋ฌผ๋ก ์ต๊ทผ์๋ ๋ชจ๋ํฐ๋ง ๋๊ตฌ(ELK๋ฑ)์ ์ฐ๋ฉด ๊ฐ๋จํ์ง๋ง, ์ด๋ฐ ๋๊ตฌ๊ฐ ์๋ ๊ณผ๊ฑฐ์๋ ์ด๋ป๊ฒ ๋ก๊ทธ๋ฅผ ๋จ๊ฒผ๋์ง ๋ฐฐ์๋ณด์.
๐ญ ๋ก๊ทธ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ณด์
๐ ์ด๋ค ๊ธฐ๋ฅ์ด ํ์ํ ๊น
์ ์ํ๋ฆ๊ณผ, ์ค๋ฅ๊ฐ ๋ฐ์ํ ์์ธํ๋ฆ์ ๊ตฌ๋ถํด์ ๋ณผ ์ ์์ด์ผ ํ๋ค.
์๋์ ๊ฐ์ด ์๋น์ค์ ์๋ฏธ์๋ ๋ชจ๋ ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ผํ๋ค.
- ๊ณต๊ฐ๋(Public) ๋ฉ์๋์ ํธ์ถ๊ณผ ์๋ต์ ๋ณด
- ๋ฉ์๋ ํธ์ถ ๋ฐ ์ฒ๋ฆฌ์ ๊ฑธ๋ฆฐ์๊ฐ
- ๊ฐ HTTP ์์ฒญ์ [๊ณ ์ ํ ํธ๋์ญ์ ID]๋ฅผ ๋ถ์ฌ์ ๊ตฌ๋ถํ๊ธฐ ์ข๊ฒ ๊ธฐ๋กํ๋ค.
๐ ํ๋์ฉ ๋ง๋ค์ด๋ณด์.
๊ทธ๋ฅ ์๋น์ค ๋ก์ง ์์๋๋ก trace.begin() ~ trace.end() ํด์ ๊ธฐ๋กํ๋ฉด ๋๋๊ฑฐ ์๋๊น?
๋ก๊ทธ๋ฅผ ์ถ๊ฐ, ์ ์ง๋ณด์ํ๋๊ฑด ๊ณจ์น์ํ ์ผ์ด๋ค. ์ ๊ทธ๋ฐ์ง ์ฝ๋๋ฅผ ํตํด ์์๋ณด์.
1. ๋ก๊ทธ๋ฅผ ๋ด๋นํ๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค.
Trace, TraceId ๋ฑ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ , ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ๋ฉ์๋ begin() end() ๋ฅผ ๋ง๋ ๋ค.
@Slf4j
@Component
public class TraceV1 {
private static final String START_PREFIX = "-->";
private static final String COMPLETE_PREFIX = "<--";
private static final String EX_PREFIX = "<X-";
public TraceStatus begin(String message) {
TraceId traceId = new TraceId();
Long startTimeMs = System.currentTimeMillis();
log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
return new TraceStatus(traceId, startTimeMs, message);
}
public void end(TraceStatus status) {
complete(status, null);
}
...
}
2. ์ด๋ฅผ ์ฌ์ฉํด์ ๋ก๊ทธ๋ฅผ ๋จ๊ธด๋ค. ์์ง๊น์ง ํ ๋งํ๋ค.
private final TraceV1 trace; // ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก
@GetMapping("/request")
public String request(String itemId) {
TraceStatus status = trace.begin("OrderController.request()"); // ๋ก๊ทธ ์์
orderService.orderItem(itemId);
trace.end(status); // ๋ก๊ทธ ์ข
๋ฃ
return "ok";
}
3. ์ด์ ์์ธ์ ์ ์ ํ๋ฆ์ ๊ตฌ๋ถํด์ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋๋ก ๋ง๋ ๋ค. ์ฌ์ฌ ๋ณต์กํด์ง๋ค
๋ก๊ทธ๋ฅผ ๋ด๋นํ๋ ๊ฐ์ฒด๊ฐ ์ง์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๊ฑฐ๋, ์๋น์ค๋ ๋น์ฆ๋์ค๋ก์ง์ ์ํฅ์ ์ฃผ๋ฉด ์๋๋ค.
@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;// ๋ก๊ทธ๋ ์ดํ๋ฆฌ์ผ์ด์
ํ๋ฆ์ ์์ ํ์ง ์๋๋ค. ์์ธ๋ ๋ค์ ๋์ง๋ค.
}
}
4. ๊ฐ ๋ฉ์๋๋ณ๋ก TraceId๋ฅผ ์ง์ ํ๋ค. ์ด๋ค ๋ฉ์๋๊ฐ ํธ์ถ๋์๋์ง, ํธ์ถ ์คํ(Level)์ ๋ก๊ทธ์ ๋จ๊ธด๋ค.
์ด๋ฅผ ์ํด์๋ Controller ๋ฟ ์๋๋ผ Service, Repository ๊น์ง ์๋์ ๊ฐ์ ๋ก๊น ๋ก์ง์ ์์ฑํด์ฃผ์ด์ผํ๋ค.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final Trace trace;
public void orderItem(TraceId traceId, String itemId) {
TraceStatus status = null;
try {
// ๋ฉ์๋ ๊น์ด๋ฅผ ๊ธฐ๋กํ๊ธฐ์ํด, beginSync() ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ค. ๊ธฐ์กด ๋ก๊น
์ ์ถ๊ฐ
status = trace.beginSync(traceId, "OrderService.orderItem()");
orderRepository.save(status.getTraceId(), itemId);
trace.end(status);
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
}
@Repository
public class OrderRepository {
private final Trace trace;
public void save(TraceId traceId, String itemId) {
TraceStatus status = null;
try {
status = trace.beginSync(traceId, "OrderRepository.save()");
/* ...์ ์ฅ ๋ก์ง... */
trace.end(status);
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
}
5. HTTP ์์ฒญ๋จ์๋ก ๊ฐ ๋ก๊ทธ๋ฅผ ๊ตฌ๋ถํ๋ค. ๊ฐ์ ์์ฒญ์ด๋ฉด ๊ฐ์ [Trace ID]๋ฅผ ๊ฐ์ง๋๋ก ๋๊ธฐํํ๋ค.
์ด๋ฅผ ์ํด์ ์๋น์ค์์ traceId๋ฅผ ์ง์ ์ง์ ํ๋๊ฒ ์๋๋ผ, ์๋์ ๊ฐ์ด ๋ก๊ทธ ๊ฐ์ฒด๊ฐ ๊ฐ์ง๊ณ ์๋๋ก ๋ฆฌํฉํ ๋งํ๋ค.
public void orderItem(String itemId) {
TraceStatus status = null;
try {
// ๊ธฐ์กด ์ฝ๋ status = trace.beginSync(traceId, "OrderService.orderItem()");
status = trace.begin("OrderService.orderItem()");
}
}
- ๊ฐ์ ๋ก๊น ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด syncTraceId()๋ฅผ ํตํด [Trace ID]๋ฅผ ๋๊ธฐํํ๊ณ ๋ฉ์๋ ํธ์ถ๋ ๋ฒจ์ +1 ํ๋ค.
- ๋ก๊น ์ด ๋๋ ๋ releaseTraceId()๋ฅผ ํตํด [Trace ID]๋ฅผ ๋๊ธฐํํ๊ณ ๋ฉ์๋ ํธ์ถ๋ ๋ฒจ -1 ํ๋ค.
@Component
public class FieldLogTrace {
private TraceId traceIdHolder; // traceId๋ฅผ ๊ฐ์ฒด๊ฐ ๋ค๊ณ ์๋๋ก ๋ณ๊ฒฝ
private void syncTraceId() {
if (traceIdHolder == null) {
traceIdHolder = new TraceId();
} else {
traceIdHolder = traceIdHolder.createNextId(); // ํธ์ถ๋ ๋ฒจ +1
}
}
private void releaseTraceId() {
if (traceIdHolder.isFirstLevel()) {
traceIdHolder = null; //destroy
} else {
traceIdHolder = traceIdHolder.createPreviousId(); // ํธ์ถ๋ ๋ฒจ -1
}
}
...
}
@Component
public class FieldLogTrace {
...
public TraceStatus begin(String message) {
syncTraceId(); // Trace Id ๋๊ธฐํ
TraceId traceId = traceIdHolder;
Long startTimeMs = System.currentTimeMillis();
log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message);
return new TraceStatus(traceId, startTimeMs, message);
}
public void end(TraceStatus status) {
complete(status, null);
}
public void exception(TraceStatus status, Exception e) {
complete(status, e);
}
private void complete(TraceStatus status, Exception e) {
Long stopTimeMs = System.currentTimeMillis();
long resultTimeMs = stopTimeMs - status.getStartTimeMs();
TraceId traceId = status.getTraceId();
if (e == null) {
log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs);
} else {
log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString());
}
releaseTraceId();
}
}
๐งจ FieldLogTrace๋ ๋์์ฑ๋ฌธ์ ๋ฅผ ๋ฐ์์ํจ๋ค.
์คํ๋ง ์ปจํ ์ด๋๋ฅผ ๊ณต๋ถํ๋ค๋ฉด ์๊ฒ ์ง๋ง, ๊ธฐ๋ณธ์ ์ผ๋ก @Component๋ ์ฑ๊ธํค ๊ฐ์ฒด๋ก ๋ฑ๋ก๋๋ค.
์ฆ ์ฑ ์คํ์์ ์ ํ๋ฒ ๋ง๋ค์ด๋๊ณ ์ฌ์ฌ์ฉํ๋ค. ์ฌ๋ฌ ํธ๋์ญ์ ์์ ๊ฐ์ ๊ฐ์ ์์ ํ๊ธฐ๋๋ฌธ์ ๋ก๊ทธ๊ฐ ์ด์ํ๊ฒ ์ฐํ๋ค.
๊ฐ์ฅ ์ฌ๊ฐํ ๋ฌธ์ ๋, ๊ฐ๋ฐํ ๋์๋ ์ด๋ฌํ ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ธ์งํ๊ธฐ ์ด๋ ต๋ค. ๊ฐ๋จํ ํ ์คํธ ์ฝ๋๋ก๋ ๋ฐ๊ฒฌํ๊ธฐ ์ด๋ ต๋ค.
/* ํด๋น ํ
์คํธ๋ ๋์์ฑ ๋ฌธ์ ๋ฅผ ์ฐพ์์ฃผ์ง ์๋๋ค. ์ ์์ ์ผ๋ก ๋์ํ๊ณ , ํ
์คํธ์ ์ฑ๊ณตํ๋ค.*/
public class FieldLogTraceTest {
FieldLogTrace trace = new FieldLogTrace();
@Test
void begin_end_level2() {
TraceStatus status1 = trace.begin("hello1");
trace.end(status2);
}
@Test
void begin_exception_level2() {
TraceStatus status1 = trace.begin("hello1");
trace.exception(status2, new IllegalStateException());
}
}
๋ํ ์ด์๋์ค ์์๋ค๊ณ ํด๋, ์ด๋์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ๋ชจ๋ฅด๊ธฐ์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋ค ๋ค์ ธ๋ณด๋ฉฐ ๋๋ฒ๊น ํด์ผํ๋ค. ๋์ฐํ๋ค.
6. ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ - ThreadLocal ์ฌ์ฉ
๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ์ํด ์๋ฐ์ java.lang.ThreadLocal ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋ณด์.|
๋ฌผ๋ก ์ฑ๊ธํค ๋น์ ์ฐ์ง์๊ณ ๋งค๋ฒ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ฑฐ๋, ์์ ์ํ ๊ฐ(Thread-Id)์ ํ๋๊ฐ ์๋ ๋ฉ์๋ ์ธ์๋ก ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ๋ฒ๋ ์๋ค. ํ์ง๋ง ๊ทธ๋ ๊ฒํ๋ฉด ์ฑ๋ฅ๋ ๊ตฌ๋ ค์ง๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ ๋ง ์ด๋ ค์์ง๋ค.
์ฌ์ฉ๋ฒ์ ๊ฐ๋จํ๋ค. ThreadLocal<๊ฐ์ฒด>๋ก ๊ฐ์ธ๊ณ , ์ปฌ๋ ์ ์ฒ๋ผ ๋ฃ๊ณ ๊บผ๋ด ์ฐ๋ฉด ๋๋ค.
public class ThreadLocalService {
private ThreadLocal<String> nameStore = new ThreadLocal<>();
// ThreadLocal.set("~")
// ThreadLocal.get()
// ThreadLocal.remove()
public String logic(String name) {
nameStore.set(name);
return nameStore.get();
}
}
๐งจ WAS(ํฐ์บฃ ๋ฑ)์์์ ThreadLocal ์ฌ์ฉ์ ์ฃผ์์ฌํญ
ThreadLocal ์ฌ์ฉ ์ดํ ์ค๋ ๋ ์ข ๋ฃ์ด์ ์ ๋ฐ๋์ .remove()๋ก ํ ๋น ํด์ ํด์ค์ผํ๋ค.
private void releaseTraceId() {
TraceId traceId = traceIdHolder.get();
if (traceId.isFirstLevel()) {
traceIdHolder.remove(); //destroy
} else {
traceIdHolder.set(traceId.createPreviousId());
}
๊ทธ ์ด์ ๋ ์ค๋ ๋ ํ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ค๋ ๋๋ ์ ๊ฑฐ๋์ง ์๊ณ ์ฌ์ฌ์ฉ๋๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๋ค.
๋ฉ๋ชจ๋ฆฌ ๋์๋ง ๋ฐ์ํ๋ฉด ๋คํ์ธ๋ฐ, ๊ธฐ์กด์ ThreadLocal์ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ฌ์ฌ์ฉํ๋ ๋์ฐํ ๋ณด์ ๋ฌธ์ ๋ ๊ฐ์ด ๋ฐ์ํ๋ค.
๐ ๋ชจ๋ ์๊ตฌ์ฌํญ์ ๋ง์กฑํ๋ค. ์ด์ ๋๋๊ฑธ๊น?
๋ก๊ทธ๋ ์ค๋ฅ ์์ด ์ ์์ ์ผ๋ก ๊ธฐ๋ก๋๋ค. ํ์ง๋ง ๋ก๊ทธ ๋๋ฌธ์ ์ฝ๋๊ฐ ์์ฒญ ๋๋ฌ์์ง๊ณ , ์๋์ ๊ฐ์ ๋ฌธ์ ์ ์ด ์๊ฒผ๋ค.
- ๋ก๊ทธ ๋๋ฌธ์ ์ ์ ๋ก์ง์ด ๋ฌด์์ธ์ง ์ฐพ๊ธฐ ์ด๋ ค์์ง๋ค. ์ ์ง๋ณด์๊ฐ ๋๋ฌด ์ด๋ ต๋ค
- ๋ก๊ทธ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ด๋ ต๋ค. ์ฑ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ ๋ ๋ก๊ทธ ๋ฉ์๋ (begin, exception, beginSync)๋ฅผ ๊ตฌ๋ถ์ง์ด ํธ์ถํด์ผํ๋ค.
- ์ฌ์ด๋์ดํํธ. ๋ชจ๋ ์๋น์ค ๊ฐ์ฒด์ Log Trace ๊ฐ์ฒด์ ๋ํ ์์กด์ฑ์ด ์๊ธด๋ค.
- Log Trace ๊ฐ์ฒด๋ฅผ ์์ ํ๋ฉด, ์๋น์ค ๊ฐ์ฒด์ ๋ณ๊ฒฝ์ด ์ ํ๋๋ค. ์ฑ ํ๋ฆ์ ์ํฅ์ ๋ผ์น๋ค.
๋ก๊ทธ ๊ธฐ๋ฅ์ ์ ๋ณํ์ง์์ง๋ง, ์ฑ ๋ก์ง์ ์ธ์ ๋ ์ง ๋ณํ ์ ์๋ค. ๊ฐ ์ฝ๋๊ฐ ์๋ก์๊ฒ ์ํฅ์ ๋ผ์ณ์๋ ์๋๋ค.
๋ก๊ทธ๋ ๋ง ๊ทธ๋๋ก ์์ด๋ ๋๋ '๋ถ๊ฐ๊ธฐ๋ฅ'์ด๋ค. ์ฑ, ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๊ฐ์ ํต์ฌ๊ธฐ๋ฅ์ ์ํฅ์ ๋ผ์ณ์๋ ์๋๋ค.
์ฑ ๋ก์ง์ ์ํฅ์ ์ฃผ์ง์๊ณ , ์ด๋ป๊ฒ ์๋น์ค์ ๊ด๋ จ๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธธ ์ ์์๊น? ๊ทธ๋ฐ๊ฒ ๊ฐ๋ฅํ๊ฐ?
๐ญ ๋ฌธ์ ํด๊ฒฐ - ๋์์ธ ํจํด
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด, ๊ฐ๋ฅํ๋๋ก ์ค๊ณ๋ฅผ ์ ํ๋ฉด ๋๋ค. ์์ฃผ ์ฌ์ฉํ๋ ์ค๊ณ ๋ฐฉ๋ฒ์ ๋ชจ์๋ ๊ฒ์ ๋์์ธํจํด์ด๋ผ ํ๋ค.
์ค๊ณ์๋ ์ธ์ ๋ ์ฌ์ฉํ ์ ์๋ ์ ๋ต์ด ์๋ค. ์ํฉ์ ๋ฐ๋ผ ๋ ๋์ ๋ฐฉ๋ฒ์ ์ ํํด์ผํ๋ค.
์์์ ๋ก๊ทธ๊ธฐ๋ฅ์ ๋ง๋ค ๋ ์๊ธด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ค์ํ ๋์์ธ ํจํด๋ค์ ๋ํด ์์๋ณด์.
๐งจ๋์์ธ ํจํด์ ์๊ฐ์์ด ๋ฌด์์ ๋ฐ๋ผํ์ง ๋ง์. ์ ์ด๋ ๊ฒ ์ฌ์ฉํ๋์ง์ ๋ํ '์ด์ '๊ฐ ๋ ์ค์ํ๋ค.
๐ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด
๋ณํํ์ง ์๋ ๊ณตํต๋ ๊ณจ๊ฒฉ(ํ)์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํ ํ๋ฆฟ, ์ฆ [์ถ์ ํด๋์ค]๋ก ๋ฝ์๋ด๋ ๋ฐฉ๋ฒ์ด๋ค.
์๋น์ค์ ๋ก๊ทธ๋ฅผ ์ถ๊ฐํ ๋, ์ฐ๋ฆฌ๋ ์๋์ ๊ฐ์ ์ฝ๋๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํ๋ค.
public void log() {
long startTime = System.currentTimeMillis();
/* ...์๋น์ค ๊ธฐ๋ฅ... */
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("๋ก๊ทธ resultTime={}", resultTime);
}
์ด๋ฅผ ํ ํ๋ฆฟ ๋ฉ์๋๋ฅผ ์ ์ฉ์์ผ ์ถ์ํ์ํค๋ฉด ๋๋ค. ์ด์ ์๋น์ค ๋ก์ง์๋ ๋ ์ด์ ๋ก๊ทธ๋ฅผ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋๋ค.
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
//๋น์ฆ๋์ค ๋ก์ง ์คํ
service();
//๋น์ฆ๋์ค ๋ก์ง ์ข
๋ฃ
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void service();
}
public class OrderService extends AbstractTemplate {
@Override
protected void service() {
/*... ๋น์ฆ๋์ค ๋ก์ง ...*/
}
}
์๋์ ๊ฐ์ด ๋คํ์ฑ์ ํ์ฉํ๋ฉด ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
๋ก๊ทธ๋ฅผ ์์ ํด๋ ์๋น์ค ๋ก์ง์ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค. ๋ ๋ถ๋ถ์ด ๊น๋ํ๊ฒ ๋ถ๋ฆฌ๋์๋ค.
AbstractTemplate template1 = new OrderService();
AbstractTemplate template2 = new ItemService();
template1.service();
template2.service();
๋จ์ 1. ๋งค๋ฒ ์๋ก์ด ์์ํด์ ์๋กญ๊ฒ ์์ฑํ๊ธฐ ๋ฒ๊ฑฐ๋กญ๋ค.
์ถ์ํํ๊ฑด ์ข์์ผ๋, ๋งค๋ฒ ์ค๋ณต๋๋ ํ ํ๋ฆฟ ํด๋์ค๋ฅผ ์์ฑํด์ผํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์๋ค.
์ด๋ ์ต๋ช ํด๋์ค๋ Java8์ ํจ์ํ ์ธํฐํ์ด์ค(๋๋ค)๋ฅผ ํ์ฉํ๋ฉด ์ฝ๊ฒ ํด๊ฒฐํ ์ ์๋ค.
AbstractTemplate itemRepository = new AbstractTemplate() {
@Override
protected void service() {
log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ");
}
};
itemRepository.service();
// ๊ฐ์ฒด๋ก ์ ๋ฌํ ๋์๋ ์ด๋ ๊ฒ ํจ์ํ ์ธํฐํ์ด์ค(๋๋ค)๋ฅผ ํ์ฉํ ์ ์๋ค.
// new ItemService( ItemRepository repository );
var ItemService = new ItemSerivce( () -> log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ") );
์ฐธ๊ณ ๋ก ์ต๋ช ์ด๋ ๋๋ค๋ก ์์ฑ๋ ํด๋์ค๋ AbstractTemplate$1, AbstractTemplate$2 ๊ฐ์ ์ด๋ฆ์ผ๋ก JVM์ ์์ฑ๋๋ค.
์ ํ ํ๋ฆฟ์ ์ ๋ค๋ฆญ๊ณผ ์ธํฐํ์ด์ค๋ก ์กฐ๊ธ ๋ค๋ฌ์ผ๋ฉด, ์๋์ ๊ฐ์ด ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
@RestController
public class OrderController {
private final OrderService orderService;
private final TraceTemplate template;
public OrderController(OrderService orderService, LogTrace trace) {
this.orderService = orderService;
this.template = new TraceTemplate(trace);
}
@GetMapping("/request")
public String request(String itemId) {
return template.execute("OrderController.request()", () -> {
orderService.orderItem(itemId);
return "ok";
});
}
}
์ฌ๊ธฐ์ ์ฌ์ฉ๋ ์ฝ๋๋ ์๋์ ๊ฐ๋ค.
public interface TraceService<T> { // ํจ์ํ ์ธํฐํ์ด์ค
T service();
}
public interface LogTrace { // ๋ก๊ทธ ๊ตฌํ์ฒด๋ฅผ ๋ณ๊ฒฝํ ์ ์๊ฒ, ์ธํฐํ์ด์ค๋ก ์ฌ์ฉํ๋ค. DIP
TraceStatus begin(String message);
void end(TraceStatus status);
void exception(TraceStatus status, Exception e);
}
public class TraceTemplate {
private final LogTrace trace;
public <T> T execute(String message, TraceService<T> callback) {
TraceStatus status = null;
try {
status = trace.begin(message);
/* ์๋น์ค ๋ก์ง ์์ */
T result = callback.service();
/* ์๋น์ค ๋ก์ง ์ข
๋ฃ */
trace.end(status);
return result;
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
}
๋จ์ 2. ์์๊ด๊ณ๋ก ์ธํด ์์กด์ฑ์ด ์๊ธด๋ค.
ํ ํ๋ฆฟ ๋ฉ์๋์์๋ ์์์ ํตํด ํด๋์ค์ ๊ณจ๊ฒฉ(Template)์ ๋ง๋ ๋ค.
์์ํด๋์ค(Log)๋ ๋ถ๋ชจํด๋์ค์ ๊ธฐ๋ฅ์ ์ ํ ์ฌ์ฉํ์ง์๋๋ฐ, ์ค๊ณ๋ฅผ ์ํด์ ๋ถ๋ชจํด๋์ค์ ์์กด์ฑ์ ๋ง๋ ๋ค.
์์ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ ์์ ์ฒ๋ผ TraceService<T> ๊ฐ์ ์ถ๊ฐ์ ์ธ ํด๋์ค๋ ํ์ํ๋ค๋ ๋จ์ ๋ ์๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๊ฐ๋จํ๋ค. ์์์ ์ฌ์ฉํ์ง ์๊ณ ํ๋๋ก ๋ฐ์์ ๋ง๋ค๋ฉด ๋๋ค. ์์กด์ฑ์ ์ ๊ฑฐํ์
โก ์ ๋ตํจํด (Strategy Pattern)
๐ ์ ๋ตํจํด
ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์, ๋ณํ์ง ์๋ ๊ณจ๊ฒฉ์ ๋ถ๋ชจํด๋์ค(Template)๋ก ๋ฝ์๋ด๊ณ , ์์์ ํตํด ์ธ๋ถ๋์์ ๊ตฌํํ๋ค.
์ ๋ต ํจํด์ ์์์ด ์๋ ํ๋๋ฅผ ์ฌ์ฉํด ์ธํฐํ์ด์ค๋ฅผ ํตํ ์์์ผ๋ก ์ธ๋ถ๋์์ ๊ตฌํํ๋ค.
public interface Strategy {
void call();
}
@Test
void test() {
Strategy myStrategy = new StrategyLogic();
Context context1 = new Context(myStrategy);
context1.execute();
// ํจ์ํ์ธํฐํ์ด์ค(๋๋ค) ํ์ฉ
Context context2 = new Context(() -> log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ"));
context2.execute();
}
@Slf4j
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
long startTime = System.currentTimeMillis();
//๋น์ฆ๋์ค ๋ก์ง ์คํ
strategy.call(); // ์์
//๋น์ฆ๋์ค ๋ก์ง ์ข
๋ฃ
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("๋ก๊ทธ resultTime={}", resultTime);
}
}
ํ ํ๋ฆฟ ๋ฉ์๋์ ๋ฌ๋ฆฌ, ํ(Strategy)๊ณผ ๊ตฌํ์ฒด ์ฌ์ด์ ์๋ฌด๋ฐ ์์กด์ฑ์ด ์๋ค.
๊ทธ๋์ ๋ก๊ทธ ๊ธฐ๋ฅ์ด ์๋๋๋ผ๋, ํ์ํ ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ ์ ํ๊ฒ ๊ตฌํํ ์ ์๋ค.
๋จ์ 1. ์ ๋ต(Strategy)์ ์ค์ ํ ํ, ๋์ค์ ๋ณ๊ฒฝํ๊ธฐ ์ด๋ ต๋ค.
๋ฌผ๋ก ๋ด๋ถ์ setStrategy(...)๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์์ง๋ง, ์ฑ๊ธํค ๊ฐ์ฒด์์ ์ํ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ Setter๋ฅผ ๋๋๊ฑด ๋งค์ฐ ์ํํ๋ค. ์ฐจ๋ผ๋ฆฌ ๊ฐ์ฒด๋ฅผ ์๋กญ๊ฒ ๋ง๋๋๊ฒ ๋์ ์๋ ์๋ค. ๋ฉํฐ์ค๋ ๋ ๋์์ฑ ๋ฌธ์ ๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๋ค.
ํ ๊ฐ์ฒด(Service)์์ ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅธ ์ ๋ต(์๊ณ ๋ฆฌ์ฆ)์ ์ ์ฉ์ํค๊ณ ์ถ๋ค๋ฉด, ์๋์ ๊ฐ์ด ๋ฉ์๋๋ก ๋ฐ์ผ๋ฉด ๋๋ค.
๋ค๋ง ์คํํ ๋ ๋ง๋ค ์ ๋ต์ ์ง์ ํด์ฃผ๋๊ฒ ๋ฒ๊ฑฐ๋กญ๋ค๋ ๋จ์ ์ด ์๋ค. ์ํฉ์ ๋ฐ๋ผ ์ ์ ํ๊ฒ ์ฌ์ฉํ์.
@Test
void test() {
ContextV2 context = new ContextV2(); // ์์ฑ ์์ ์๋ ๋น์๋๋ค.
// ์ฌ์ฉํ๋ ์์ ์ ์ ์ ํ Strategy ๊ตฌํ์ฒด๋ฅผ ์ ๋ฌํ๋ค.
context.execute(() -> log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ"));
context.execute(() -> log.info("๋น์ฆ๋์ค ๋ก์ง2 ์คํ"));
// ๊ณ ์ ํ ์ํ ๊ฐ(ํ๋)๊ฐ ์๋ค๋ฉด, ๋งค๋ฒ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ง ์๊ณ ์ฌ์ฌ์ฉํ ์๋ ์๋ค.
context.execute(myStrategy());
}
๐ ํ ํ๋ฆฟ ์ฝ๋ฐฑํจ์(callback function)
์ฐธ๊ณ ๋ก ํจ์๋ฅผ ์ธ์(ํ๋ผ๋ฉํ)๋ก ๋๊ฒจ์ฃผ๋๊ฑธ callback ํจ์(call-after function)๋ผ๊ณ ๋ถ๋ฅธ๋ค.
๋น์ฅ ์คํํ์ง ์๊ณ , ํจ์๋ฅผ ์ธ์๋ก ๋ฐ์ ํ์ํ ์์ ์ ์คํํ๋ค๋ ์๋ฏธ.
์ฝ๋ฐฑ ํจ์๋ ๋์์ธ ํจํด์ ์๋๊ณ , ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์ ๋ง์ด ์ฌ์ฉํ๋ ๊ธฐ๋ฒ์ด๋ค.
๋ฐฉ๊ธ ์ธ๊ธํ [๋ฉ์๋๋ฅผ ์ด์ฉํ ์ ๋ตํจํด]๊ณผ ๋น์ทํ๋ค. ๋ค๋ฅธ ์ ์ ์ฝ๋ฐฑํจ์๋ฅผ ์ฌ์ฉํ๋ค๋๊ฑฐ ์ ๋
์๋ฐ๋ ๋ฉ์๋๊ฐ ์ผ๊ธ์๋ฏผ (์ธ์๋ก ์ ๋ฌ, ๋ฆฌํด ๊ฐ๋ฅ)ํ ๋์์ด ์๋๊ธฐ์ ์ฝ๋ฐฑํจ์ ๊ตฌํ์ด ๋ถ๊ฐ๋ฅํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ์ํด JDK8๋ถํฐ๋ ํจ์ํ ์ธํฐํ์ด์ค์ ํจ์๋ฅผ ์ฝ๊ฒ ์ ๋ฌํ๊ฒ ํด์ฃผ๋ Function-Class API๋ฅผ ์ ๊ณตํด์ค๋ค.
์ฐธ๊ณ ๋ก ์คํ๋ง์ JdbcTemplate, RestTemplate, TransactionTemplate, RedisTemplate ๋ฑ์ ์ฝ๋ฐฑ ํจํด์ ์ด์ฉํ๋ค.
์๋ ์์ ์์๋ Runnable์ด๋ผ๋ ํจ์ํ ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํ์ผ๋, ํ์ํ๋ค๋ฉด ์ธํฐํ์ด์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด๋ ๋๋ค.
์) public void execute(MyCallback callback)
public class TimeLogTemplate {
public void execute(Runnable callback) {
long startTime = System.currentTimeMillis();
//๋น์ฆ๋์ค ๋ก์ง ์คํ
callback.run(); //์์
//๋น์ฆ๋์ค ๋ก์ง ์ข
๋ฃ
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
@Test
void callback() {
TimeLogTemplate template = new TimeLogTemplate();
template.execute(() -> log.info("๋น์ฆ๋์ค ๋ก์ง1 ์คํ"));
template.execute(() -> log.info("๋น์ฆ๋์ค ๋ก์ง2 ์คํ"));
}
๐ ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ์
์๋ฌด๋ฆฌ ์ถ์ํ๋ฅผ ํ๊ณ ์์กด์ฑ์ ๋์ด๋ธ๋ค๊ณ ํ๋ค, Template ์์ฒด๋ฅผ ์์จ ์๋ ์๋ค.
์ฌ์ฉ๋ฒ์ ๊ฐ๋จํด์ก์ง๋ง ๊ฒฐ๊ตญ ๊ฐ ์๋น์ค์์ Template ์ธํฐํ์ด์ค๋ ํธ์ถํด์ค์ผํ๋ค.
๐ค ์คํ๋ง์ ๋๋๋ฐ์?
๋ง๋ค. ์คํ๋ง์ ๋ค์ด๋๋ฏน ํ๋ก์๋ฅผ ํ์ฉํ AOP๋ฅผ ํตํด ํ๋ก์ ํจํด๊ณผ ๋ฐ์ฝ๋ ์ดํฐ ํจํด์ ๊ตฌํํ์ฌ ํด๊ฒฐํ๋ค.
์ฌ์ค ๋ฐ์ฝ๋ ์ดํฐ ํจํด๊ณผ ํ๋ก์ ํจํด์ ๊ฑฐ์ ๋น์ทํ๋ค. ๋์์ธํจํด์ '๋ชจ์'์ด ์๋ '์๋'์ ๋ฐ๋ผ ๊ตฌ๋ถํ์.
- ํ๋ก์ ํจํด: ์ ๊ทผ์ ์ด๊ฐ ๋ชฉ์
- ๋ฐ์ฝ๋ ์ดํฐ ํจํด: ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ๊ฐ ๋ชฉ์
2021.08.17 - [๐ฑBackend/Spring Core] - ์๋ฐ AOP์ ๋ชจ๋ ๊ฒ(Spring AOP & AspectJ)
์๋ฐ AOP์ ๋ชจ๋ ๊ฒ(Spring AOP & AspectJ)
์ด ๊ธ์ ์ฌ์ ์ง์์ด ์๋ค๋ฉด ์ฝ๊ธฐ ์ด๋ ค์ธ ์ ์๋ค. ๋ฐ์ดํธ์ฝ๋์ ๋ฆฌํ๋ ์ ์ ๋ชจ๋ฅธ๋ค๋ฉด ์๋์ ๊ธ์ ๊ผญ ์ฝ์ด๋ณด๋๋กํ์. 2021.08.17 - [๊ธฐ๋ณธ ์ง์/Java ๊ธฐ๋ณธ์ง์] - ๋ฐ์ดํธ์ฝ๋ ์กฐ์(๋ฆฌํ๋ ์ , ๋ค์ด๋๋ฏน
jiwondev.tistory.com
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ํ์ผ ์ ๋ก๋, ๋ค์ด๋ก๋ (0) | 2021.08.31 |
---|---|
Spring ํ์ ์ปจ๋ฒํฐ (Converter, Formatter) (0) | 2021.08.31 |
Spring ์์ธ์ฒ๋ฆฌ - API, @ExceptionHandler (0) | 2021.08.30 |
Spring ์์ธ์ฒ๋ฆฌ - ์ค๋ฅํ์ด์ง(404,500) (0) | 2021.08.30 |
Spring Login#2 ํํฐ, ์ธํฐ์ ํฐ, ArugmentResolver (0) | 2021.08.30 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev