강의 하나를 완강해야 겠다고 생각이 들었는데 빠르게 보고 싶은 강의가 생겼다. 강의 들으면서 정리도 겸하고 싶어 주저리 주저리 이 페이지에 옮겨놓으려고 한다. 조금 길어지면 2편으로 .. 

로그 추적기

프로젝트에 병목이 생긴다면 로그를 통해 확인해봐야 한다. 아래 요구사항으로 로그를 개선하는 작업이다. 

  • 시간
  • 정상호출 과 예외호출 구분
  • 메서드 호출 깊이 구분

실제로 소스를 보면 알겠지만 로그 추적기 수정이 일어날 때마다 피처들에 대한 버전을 계속 관리해야 한다. 

이 말은 로그 추적기를 달아야할 피처가 많아지면 많이질수록 그만큼 소스양이 나온다는 뜻..! 

아래 구조에서는 traceId를 넘겨줘야 하는 구조여서 새 클래스를 생성할 때마다 의존관계도 계속 추가되고 그러면 응집도가 떨어지고 결합성이 높아질 수 밖에 없게 된다. 

버전이 계속 올라간다.

 

그러면 traceId 라는걸 파라미터로 넘기지 말고 필드로 동기화를 시켜보자. 이런 경우 개선할 수는 있지만 로컬에서 쓸때가 아닌 실제 상용에 배포를 하게 되면 동시성 문제가 발생하게 된다. 

구현체 FieldLogTrace를 보고
필드로 TraceId를 갖고 있다.

이렇게 코드를 구성하고 cmd+r 로 두번 실행하게 되면 트랜잭션 id 가 bcc3adca 로 동일한것을 볼 수 있다. 뭔가 꼬였다는 뜻이다. 

스레드가 서로 다른데 트랜잭션 id가 서로 똑같다. 

logTrace 에 싱글톤으로 구성되어 있어서 문제가 발생한다. 

그러면 이러한 문제를 어떻게 해결 할 수 있을까? 

ThreadLocal를 사용해보자.

TheadLocal은 특정 스레드만 접근할 수 있는 공간을 말한다. 같은 인스턴스 스레드 로컬 필드에 접근해도 문제가 없다. 

threadLocal은 아래처럼 사용한다. 하지만, 사용하고 나면 threadLocal은 지워줘야 한다. 그렇지 않으면 메모리 누수같은것이 발생할 수 있다고 한다. 

ThreadLocal<String> traceIdHolder = new ThreadLocal<>()

기존 코드에서 traceId 로 값을 추적할 때 아래 코드 처럼 변경하게 된다.

private void syncTraceId() {
        TraceId traceId = traceIdHolder.get(); //threadLocal에 있는 값을 꺼내온다. 
        if (traceId == null) {
            traceIdHolder.set(new TraceId());
        } else {
            traceIdHolder.set(traceId.createNextId());
        }
    }

실제로 LogTraceConfig 에서 보면 아래처럼 반환하는 구현체만 변경해주면 LogTrace가 bean으로 등록되어 사용하는곳에서는 실제 구현체의 메소드를 꺼내와 사용할 것이다. 

근데 실제 구현체에 @Component 달아주면 되긴 하는데 ... 이렇게 Config 로 관리하는게 관리측면에서 좀 더 좋을거 같다. 

결과도 두번 연속 호출했을 때 위와 같이 transaction Id가 같다던지 Controller가 두번 호출된다던지 문제가 없이 해결할 수 있다. 

 

 

그러면 주의사항은 없을까? 

이미 threadLocal 에 할당된 값이 있는 상태에서 해당 thread를 제거하지 않고 고대로 thread pool에 반납 시 다음 task에 우연히 threadLocal에 값이 들어있는 thread가 할당되었을 때 threadLocal의 값을 가져올 수 있기 때문에 문제가 생길 수 있다. 

꼭 기억해두자. threadLocal.remove() 로 제거해야한다. 

 

템플릿 메서드 패턴 및 콜백 패턴

위와 같이 로그 추적기를 붙이는건 좋은데 서비스 레이어에 메서드 콜백할때마다 try-catch로 위 아래 로그 관련 메서드를 다 작성하려하니 가독성이 좋지 않고 코드가 계속 늘어나는거 같다.

그러니까 핵심기능은 비즈니스 로직에 대한것은 몇줄 없고 부가기능인 로그 관련 코드가 많이 심어져있는 상태이다. 

그래서 분리해서 모듈화 하려고 한다. 

템플릿 메서드 패턴에서 상위에 Abstract Class 영역에 변하지 않은 영역을 다 넣어준다. 

변하는 부분을 자식  클래스로 만들어서 해결한다. 

https://zhiminzhan.medium.com/software-design-pattern-by-example-template-method-d1709292cc7d

code example 1 

code example 2 

이런식으로 상속해서 call() 부분에 재정의해서 사용한다. 

 

이렇게 팩토리 메서드 패턴을 사용하면 변경이 일어났을 때 한번만 변경 하면 되므로 (execute 영역) 단일 책임 원칙을 잘 가지고 있다고 설명할 수 있겠다. 

 

요즘들어 글쓰는거 참 재밌는듯..

 

코드 정리

github 참고 url -  https://github.com/lllilllilllilili/dev-lab/pulls?q=is%3Apr+is%3Aclosed

'Spring' 카테고리의 다른 글

Spring Batch 멀티 프로세스  (1) 2024.09.09
Criteria API  (0) 2023.01.28
의존성 주입  (1) 2023.01.21
Spring Security  (0) 2021.12.14
빈 생명주기 & 콜백  (0) 2021.12.14