키오스쿨의 수퍼어드민 개발이 한창이다. 물론 프론트 코드의 리팩토링이 대부분이지만 나도 무언갈해야겠다 싶어서 서버 코드를 리팩토링하기로 했다.

사실은 리팩토링보다 테스트 코드부터 모든 서비스 코드에 붙여보려고 했었는데 현 코드 구조로는 테스트 코드 작성이 쉽지 않았다. 내가 같은 클래스에 있는 함수를 부르고 있어서 그랬는데, 이를 해결하기 위해서 facade 패턴 적용을 시작하고 있었다. 적용을 하다보니 내가 옛날부터 더 좋은 방법이 없을까 하면서 눈여겨 보던 코드가 눈에 확 들어왔다. 바로 controller에서 받는 authentication이다.

기존에는 authentication에서 username을 받기 위해 아래와 같이 사용하고 있었다.

fun getWorkspaces(authentication: Authentication): List<Workspace> {
    val username = (authentication.principal as CustomUserDetails).username
    return workspaceService.getWorkspaces(username)
}

그러다보니 항상 중복되는 코드가 너무 많이 생기게 되었다. 다만 한 줄이기 때문에 어느정도 참고 넘어갔을 뿐!!

이번에 리팩토링 하는김에 이걸 해결할 수 있는지에 대해서 한 번 찾아봤다.

controller에서 공통 파라매터를 받을 수 있는지에 대해서 찾아보았고 이리저리 찾아보다가 ArgumentResolver라는 개념을 알게되었다.

ArgumentResolver는 request로부터 값을 참조하거나 객체를 생성해서 Handler(controller)의 파라매터 바인딩에 사용하는 객체이다. 사실상 interceptor랑 비슷한 친구이다. 보통 토큰 처리부분은 interceptor에서 담당하고 해당 토큰에서 필요한 정보 뽑아내기는 resolver에서 많이 한다고 한다.

해당 resolver를 사용하기 위해서 일단 annotation 하나를 만들겠다. controller 파라매터에 사용할 annotation으로 나중에 resolver 구현에도 사용된다.

package com.kioschool.kioschoolapi.common.annotation

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Username

이제 resolver를 만든다.

package com.kioschool.kioschoolapi.common.resolver

import com.kioschool.kioschoolapi.common.annotation.Username
import org.springframework.core.MethodParameter
import org.springframework.stereotype.Component
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.method.support.ModelAndViewContainer

@Component
class AuthenticationArgumentResolver : HandlerMethodArgumentResolver {
    override fun supportsParameter(parameter: MethodParameter): Boolean {
        return parameter.getParameterAnnotation(Username::class.java) != null &&
                parameter.parameterType == String::class.java
    }

    override fun resolveArgument(
        parameter: MethodParameter,
        mavContainer: ModelAndViewContainer?,
        webRequest: NativeWebRequest,
        binderFactory: WebDataBinderFactory?
    ): Any? {
        return webRequest.userPrincipal?.name
    }
}