안녕하세요!
이번 글에서는 Bridge-Api 라이브러리의 또 다른 핵심 기능인 파라미터 바인딩 구현에 대해 다루어보려고 합니다.
파라미터 바인딩은 HTTP 요청의 다양한 부분(경로 변수, 쿼리 파라미터, 요청 본문 등)을 컨트롤러 메서드의 파라미터에 자동으로 매핑해주는 기능입니다.
1. 파라미터 바인딩의 필요성
Spring과 같은 웹 프레임워크를 사용해보신 분들은 @PathVariable
, @RequestParam
, @RequestBody
등의 어노테이션이 익숙하실 것입니다. 이러한 어노테이션들은 HTTP 요청의 데이터를 쉽게 처리할 수 있게 해주죠.
Bridge-Api에서도 이와 유사한 기능이 필요했습니다. 다음과 같은 목표를 가지고 구현을 시작했습니다:
- 경로 변수를 쉽게 추출할 수 있어야 함
- 쿼리 파라미터를 자동으로 변환할 수 있어야 함
- JSON 요청 본문을 자동으로 객체로 변환할 수 있어야 함
- 헤더 값을 쉽게 가져올 수 있어야 함
2. 어노테이션 정의
먼저, 필요한 어노테이션들을 정의했습니다:
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class PathVariable(val key: String)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Query(val key: String)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class JsonBody
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Header(val key: String)
각 어노테이션의 역할은 다음과 같습니다:
@PathVariable
: URL 경로의 변수 부분을 파라미터로 바인딩@Query
: 쿼리 스트링의 파라미터를 바인딩@JsonBody
: JSON 요청 본문을 객체로 변환하여 바인딩@Header
: 헤더 값을 파라미터로 바인딩
3. 파라미터 바인딩 구현
파라미터 바인딩의 핵심 로직은 BaseService
클래스에 구현했습니다:
class BaseService(private val objectMapper: ObjectMapper) : BridgeService {
override suspend fun serve(ctx: RequestContext): BridgeResponse =
BridgeResponse(invokeFunction(ctx))
private suspend fun invokeFunction(ctx: RequestContext): Any? {
val args = ctx.function.valueParameters.map { param ->
when {
param.findAnnotation<JsonBody>() != null -> {
val type = object : TypeReference<Any>() {
override fun getType() = param.type.javaType
}
objectMapper.readValue(ctx.body?.serializeToJson(objectMapper), type)
}
param.findAnnotation<Header>() != null -> {
convertParamValue(
param.type.javaType,
ctx.headers[param.findAnnotation<Header>()!!.key]
)
}
param.findAnnotation<Query>() != null -> {
convertParamValue(
param.type.javaType,
ctx.queryParameters[param.findAnnotation<Query>()!!.key]
)
}
param.findAnnotation<PathVariable>() != null -> {
convertParamValue(
param.type.javaType,
ctx.pathVariables[param.findAnnotation<PathVariable>()!!.key]
)
}
else -> null
}
}.toTypedArray()
return if (ctx.function.isSuspend)
ctx.function.callSuspend(ctx.controller, *args)
else
ctx.function.call(ctx.controller, *args)
}
}
여기서 핵심적인 부분들을 살펴보겠습니다:
- 타입 변환 로직
private fun convertParamValue(paramType: java.lang.reflect.Type, paramValue: String?): Any? = when (paramType) { String::class.java -> paramValue Int::class.java, Integer::class.java -> paramValue?.toInt() Boolean::class.java -> paramValue?.toBoolean() Float::class.java -> paramValue?.toFloat() Double::class.java -> paramValue?.toDouble() Long::class.java -> paramValue?.toLong() else -> paramValue }
- JSON 바디 처리
val type = object : TypeReference<Any>() { override fun getType() = param.type.javaType } objectMapper.readValue(ctx.body?.serializeToJson(objectMapper), type)
4. 실제 사용 예시
이제 컨트롤러에서 어떻게 사용되는지 살펴보겠습니다:
@Get("/:id")
fun getUserById(@PathVariable("id") id: Long): ApiCommonResponse<UserResDto> {
return ApiCommonResponse(
status = 0,
message = "success",
data = userService.getUserById(id)
)
}
@Post("/search")
fun searchUsers(
@Query("name") name: String?,
@Query("age") age: Int?,
@Header("Authorization") token: String
): ApiCommonResponse<List<UserResDto>> {
// 구현
}
@Post("")
fun createUser(@JsonBody userReq: UserReqDto): ApiCommonResponse<UserResDto> {
return ApiCommonResponse(
status = 0,
message = "success",
data = userService.createUser(userReq)
)
}
5. 고려사항
구현 과정에서 몇 가지 고려해야 할 사항들이 있었습니다:
Null 안전성
val value = ctx.queryParameters[key]
?: if (param.type.isMarkedNullable) null else throw IllegalArgumentException("Required parameter '$key' is missing")
타입 변환 실패 처리
try {
convertParamValue(param.type.javaType, value)
} catch (e: Exception) {
throw IllegalArgumentException("Failed to convert parameter '$key' to ${param.type}")
}
성능 최적화
- 리플렉션 사용을 최소화하기 위해 파라미터 정보를 캐싱
- 타입 변환 로직을 최적화하여 불필요한 객체 생성 방지
6. 마치며
파라미터 바인딩 구현을 통해 다음과 같은 이점을 얻을 수 있었습니다:
- 편의성: 개발자가 직접 파라미터를 파싱하고 변환할 필요가 없어졌습니다.
- 타입 안전성: 컴파일 시점에 타입 오류를 발견할 수 있습니다.
- 코드 간결성: 반복적인 파라미터 처리 코드가 제거되었습니다.
- 유지보수성: 파라미터 처리 로직이 중앙화되어 관리가 용이합니다.
이것으로 Bridge-Api의 주요 기능들에 대한 설명을 모두 마쳤습니다.
다음 글에서는 Bridge-Api를 실제 프로젝트에 적용한 경험과 개선된 사항에 대해 이야기 해보도록 하겠습니다.
- 라이브러리 레포지토리: https://github.com/shiniseong/bridge-api
728x90