[수미수의 개발 브로구]

[WebFlux] WebFlux AOP 사용하기 본문

Language & Framework/WebFlux

[WebFlux] WebFlux AOP 사용하기

수미수 2023. 8. 21. 19:14
반응형

들어가기 전

  Spring WebFlux 는 리액티브 프로그래밍 모델을 기반으로 하며, 일반적으로 AOP (Aspect-Oriented Programming) 은 Spring 의 핵심 기능 중의 하나이다.  해당 기능은 어플리케이션에서 공통적으로 사용되는 기능을 분리하여 관점 지향 프로그래밍을 통해 공통 기능을 처리 할 수 있게 해준다. 이러한 공통 기능은 일반 Spring 에서 Filter, Interceptor, AOP 를 통해서 구현 할 수 있다.

  Spring WebFlux 는 비동기/논블럭킹으로 동작 하기 때문에, AOP를 사용하여 비동기 요청 및 응답 처리 과정에서 로깅, 및 기타 공통 기능을 분리하여 재사용성과 유지 보수성을 높일 수 있다.

  AOP 를 사용하기 위해서는 WebFlux 에서도 @Aspect 어노테이션을 사용하여 AOP 클래스를 정의하고, 적용하고자 하는 메서드에 Advice 를 정의하여 사용하면 된다.

  해당 글에서는 요청/응답 처리 구간에서 응답 시간을 측정하여 응답 값에 추가하여 응답 하는 예제를 통해서 AOP를 어떻게 사용 하는지에 대해서 이야기 하고자 한다.

코드 분석

@Around("execution(* com..*Controller.*(..))")
    fun process(pjp: ProceedingJoinPoint): Any {

        val signatureArgs = pjp.args
        
        // 1. 시간 측정 시작
        val stopWatch = StopWatch()
        stopWatch.start()
        val strRequest = StringBuilder("[RestRequest]")

        if (signatureArgs.isNotEmpty()) {
            val request = signatureArgs[0] as Any
            strRequest.append(" : ${gson.toJson(request)}")
        }
        // 2. 요청 Body 로깅
        log.info(strRequest.toString())

        return (pjp.proceed() as Mono<ResponseEntity<Any>>)
            .flatMap {
                stopWatch.stop()
        // 3. 응답 시간 측정
                val response = ResponseEntity<Any>(RestResponse(ApiResponseCode.OK.code, ApiResponseCode.OK.message, stopWatch.totalTimeMillis.toString(), "","", it.body), it.statusCode)
                log.info("[RestResult] : ${gson.toJson(response)}")
        // 4. 응답
                Mono.just(
                    response
                )
            }
    }

  위 소스 코드는 비동기 요청/응답 구간에서 공통 기능을 AOP 로 구현한 예제이다. 그 기능으로는 Presentation Layer 인 Controller 로 호출 전 시간을 측정 한 뒤 , 해당 Controller 메서드를 나갈 때 까지의 경과 시간을 응답 값으로 같이 추가 하는 예제 이다.

시간 측정

  시간 측정은 아래와 같이 StopWatch 클래스를 사용하여, 시간 측정을 시작 한다.

// 1. 시간 측정 시작
val stopWatch = StopWatch()
stopWatch.start()

요청 값 로깅

  요청 값에 대한 로깅은, 아래와 같이 SignatureArgs 객체의 값이 없을 경우, 요청 된 json 값을 String 으로 변환하여 출력 되도록 한다.

val strRequest = StringBuilder("[RestRequest]")

if (signatureArgs.isNotEmpty()) {
	val request = signatureArgs[0] as Any
	strRequest.append(" : ${gson.toJson(request)}")
}
// 2. 요청 Body 로깅
log.info(strRequest.toString())

응답 

  함수형 프로그래밍 스타일로 proceed 를 호출 한 결과 값을 핸들링 하여, 시간 객체를 종료 시킨 후 RestResponse 객체에 담은 후 응답 객체를 만든 후 Mono 객체로 변환하여 Return 한다.

return (pjp.proceed() as Mono<ResponseEntity<Any>>)
        .flatMap {
            stopWatch.stop()
    // 3. 응답 시간 측정
            val response = ResponseEntity<Any>(RestResponse(ApiResponseCode.OK.code, ApiResponseCode.OK.message, stopWatch.totalTimeMillis.toString(), "","", it.body), it.statusCode)
            log.info("[RestResult] : ${gson.toJson(response)}")
    // 4. 응답
            Mono.just(
                response
            )
        }

RestResponse Response DTO 샘플

  아래는 Response 로 사용되는 DTO 클래스의 구조이다. 응답 지연시간 정보는 아래 elapsedTime 필드에 적용되어 응답 하게 된다.

data class RestResponse<T> (

        val returnCode:String,

        val returnMsg:String,

        val elapsedTime: String,

        val errorCode: String,

        val errorMsg: String,

        val returnData: T
)

 

반응형