[디자인 패턴] 로그 기능 적용 : 템플릿 메서드 패턴

Heechul Yoon·2022년 6월 4일
0

문제

WAS서버에 요청이 들어왔다가 http response로 나가기까지 몇분이 걸리는지 측정해야하는 상황을 가정해보자.

@PostMapping("/postings/posting")
public ResponseEntity createPosting(@RequestBody @Valid CreatePostingRequest body) {
    Long startTimeMs = System.currentTimeMillis(); // 시작
    postingService.createPosting(body.getTitle(), body.getContent());
    Long endTimeMs = System.currentTimeMillis(); // 끝
    
    log.info("time - {}", endTimeMs - startTimeMs); // 걸린 시간

    return new ResponseEntity<>(null, null, HttpStatus.CREATED);
        
}

이런식으로 표시할 수 있다.

단점이 있다면, 모든 기능의 비지니스 로직 전과 후에 시간을 찍어주는 기능을 넣어야 한다. 결국 이런식의 설계는 유지보수를 힘들게 만든다. 만약 1000개 기능에 위와 같은 성능측정 로그를 달아줬다가 로깅 형식을 바꾼다거나, 다 바꿨는데 예전것이 좋아서 다시 돌아간다면 더이상 개발자가 하고싶지 않을것이다.

여기서 생각해볼 수 있는 아이디어는 모든 로깅부분은 동일할 것이고 바뀌는것은 비지니스 로직이니 로깅을 하는 기능과 비지니스적 기능을 분리하는 것이다.

OOP 특성 중 다형성을 활용한 템플릿 메서드 패턴을 통해서 이것이 가능하다.

템플릿 메서드 패턴

이름 그대로 공통적으로 적용되는 템플릿 기능을 하는 메서드가 있는 것이다.
여기서의 아이디어는 공통되는 로깅 부분을 수행하는 템플릿이 있고, 그 템플릿 안에서 비지니스로직이 구현된 메서드를 호출하는 것이다. 여기서 비지니스로직은 모든 api마다 다를 것이기 때문에 다형성이 적용되어야 할 부분이다.

public abstract class LogTemplate<T> {

    public T execute(String message) {
        Long startTimeMs = System.currentTimeMillis(); // 시작
        T result = call();
        Long endTimeMs = System.currentTimeMillis(); // 끝
    
        log.info("time - {}", endTimeMs - startTimeMs); // 걸린 시간
        return result;
        
    }

    protected abstract T call();
}

우선 템플릿 담당을 할 추상클래스를 만들었다.
execute함수 안에서 시간 측정 "시작"과 "끝" 사이에 비지니스로직이 들어갈 자리를 call함수로 대체한다. call이라는 추상 메서드는 다형적으로 구형될 예정이다. call 메서드의 다형적 구현은 구체클래스를 만들어도 되지만 익명 내부 클래스를 사용하도록 한다.

@PostMapping("/postings/posting")
public ResponseEntity createPosting(@RequestBody @Valid CreatePostingRequest body) {
    LogTemplate<List<PostingsDto>> logger = new LogTemplate<Void>() {
        @Override
        protected Void call() {
            postingService.createPosting(body.getTitle(), body.getContent());
        } // [1]
    };
    logger.execute("PostingController.createPosting");
    
    return new ResponseEntity<>(null, null, HttpStatus.CREATED);
        
}

컨트롤러에서 서비스에 있는 createPosting을 바로 호출하는 것이 아닌, LogTemplate이라는 추상클래스를 상속받는 임시 클래스를 만들어서 인스턴스로 만들어주고, logger 인스턴스 안에 있는 execute 함수를 호출해서 간접적으로 [1]에서 구현해준 call메서드를 실행한다.

단점

의미없는 데이터 생성

자식클래스가 부모클래스에서 사용하지 않는 기능 혹은 데이터도 상속 받아서 들고있어야 한다. 지금처럼 단순한 로깅의 경우 추상 템플릿 클래스에 특별한 멤버변수나 메서드가 없지만 언제 어떻게 추가질되 모르고, 추가된다면 구체클래스의 경우 이 모든걸 상속받아서 들고있게 된다. 요청 몇십, 몇백건의 경우에는 영향이 없겠지만, 수십만건에 달하는 요청의 경우에는 가비지 컬랙터가 수집해야할 데이터가 많아지고, 가비지 컬렉터가 도는동안은 어플리캐이션이 잠시 멈추기 때문에(Stop-the-world 상태) 성능상 좋지 않다.

부모클래스와의 의존성

추상클래스를 상속받는다는 것은 결국 부모클래스와 의존성이 생긴다는 뜻이다. 만약 부모클래스에 추상메서드가 하나 추가된다거나, 어떤 변화가 생기면 상속받는 모든 자식클래스에게 영향이 가게 된다. 결국 높은 결합도로 인해 유지보수성이 떨어진다

모든 코드 수정

컨트롤러에서 직접 시간을 찍을 필요는 없지만 여전히 컨트롤러를 수정해야하며, 기능이 n개라면 n개의 컨트롤러를 수정해야 한다.

다음에는 이런 단점들을 고려한 다른 디자인패턴을 적용해보겠다

profile
Quit talking, Begin doing

0개의 댓글