Spring

Spring 템플릿 콜백 패턴(Template Callback Pattern)

제리 . 2022. 3. 6. 21:58

콜백이란?

프로그래밍에서 콜백(callback) 또는 콜 애프터 함수(call-after function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다. (위키백과 참고)

callback은 코드가 호출은 되는데 코드를 넘겨준 곳의 뒤에서 실행된다는 의미이다.

 

템플릿 콜백 패턴이란?

GOF의 디자인 패턴은 아니다. 전략 패턴에 특정 방식을 사용하는데 스프링 내부에서 이런 방식이 자주 사용되기 때문에, 스프링에서는 이렇게 부른다. 

스프링에서는 JdbcTemplate, RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다.

 

예를 들어 템플릿 콜백 패턴을 사용하여 비즈니스 로직의 실행시간을 측정한다고 하자.

call(변경되는 알고리즘)은 Callback인터페이스로 선언된다. 

fun interface Callback {
    fun call()
}

Callback의 call은 execute에서 전달받아 호출된다.   

class TimeLogTemplate {

    private val logger = LoggerFactory.getLogger(this::class.simpleName)

    fun execute(callback: Callback) {
        val startTime = System.currentTimeMillis()
        // 비즈니스 로직 실행
        callback.call() // 위임
        // 비즈니스 로직 종료
        val endTime = System.currentTimeMillis()
        val resultTime = endTime - startTime
        logger.info("resultTime=$resultTime")
    }
}

 템플릿 콜백 패턴은 이렇게 사용된다.

    @Test
    fun `익명 클래스를 사용한 템플릿 콜백 패턴 적용`() {
        val template = TimeLogTemplate()
        template.execute(object : Callback {
            override fun call() {
                logger.info("비즈니스 로직1 실행")
            }
        })

        template.execute(object : Callback {
            override fun call() {
                logger.info("비즈니스 로직2 실행")
            }
        })
    }

    @Test
    fun `람다를 사용한 템플릿 콜백 패턴 적용`() {
        val template = TimeLogTemplate()
        template.execute { logger.info("비즈니스 로직1 실행") }
        template.execute { logger.info("비즈니스 로직2 실행") }
    }

템플릿 콜백 패턴은 전략 패턴에서 템플릿과 콜백이 강조된 패턴이다.

위에서 Strategy는 Call을 의미한다. 클라이언트가 Strategy를 직접 실행하는 게 아니라 Context.execute에 전략을 넘기고 Context뒤에서 Strategy가 실행된다.

 

 

템플릿 콜백 패턴 예시 - RedisTemplate

RedisTemplate은 템플릿 콜백 패턴으로 되어있다.

먼저, 위 예시의 Callback인터페이스에 해당하는 RedisCallback이 있다. 여기서 doInRedis가 call역할을 한다.

다음은 Context에 해당하는 부분이다. RedisCallBack(변경 가능한 알고리즘/전략)을 파라미터에서 받아오고 호출한다.

직접적인 사용 예시를 살펴보자 RedisTemplate의 expire는 아래와 같이 구성되어있다.

만약 클라이언트가 RedisTemplate를 사용해서 expire를 호출하면 아래와 같이 실행된다.

1. 클라이언트에서 expire를 호출하면 expire내부에 execute가 호출된다. 이때 콜백(expire에 대한 구현체)을 람다로 넘긴다.

2. execute메서드에서 콜백의 doInRedis가 호출된다.

 

다른 예시를 보자. RedisTemplate의 delete는 아래와 같이 구성되어있다.

만약 클라이언트가 RedisTemplate를 사용해서 delete를 호출하면 아래와 같이 실행된다.

1. 클라이언트에서 expire를 호출하면 expire내부에 execute가 호출된다. 이때 콜백(delete에 대한 구현체)을 람다로 넘긴다.

2. execute메서드에서 콜백의 doInRedis가 호출된다.

 

위의 두가지 예시를 보면 알 수 있는 것 처럼 콜백에 어떤 구현체를 넘겨주는지에 따라 유연하게 변경되는 부분만 교체할 수 있다.