본문 바로가기
작디 작은 나만의 라이브러리/BridgeApi

[신입 개발자의 '작디 작고 작디 작고 자그만' 첫 라이브러리 제작기] 3. Bridge-Api 에러핸들러 구현

by 시니성 2024. 10. 3.

안녕하세요!
회사에서 프로젝트가 산더미 처럼 떨어져서, 아주 오랜만에 글을 이어가려 합니다.
오늘은 Bridge-Api 라이브러리의 에러핸들러 구현에 대해 이야기해보려고 하는데요!
에러핸들러는 라이브러리의 중요한 부분으로, 일관성 있고 효율적인 에러 처리를 가능하게 합니다.

1. 에러핸들러의 필요성

에러핸들러를 만들게 된 주요 이유는 다음과 같습니다:

  1. 일관성: 모든 에러를 일관된 방식으로 처리할 수 있습니다.
  2. 중앙화: 최상위 레벨에서 에러를 처리함으로써 코드 중복을 줄이고 관리를 용이하게 합니다.
  3. 유연성: 다양한 타입의 에러에 대해 각각 다른 처리 방식을 적용할 수 있습니다.
  4. 디버깅 용이성: 모든 에러가 한 곳에서 처리되므로 디버깅이 쉬워집니다.

2. 에러핸들러 인터페이스 설계

먼저, 에러핸들러의 인터페이스를 설계했습니다. 간단하면서도 유연한 구조를 위해 함수형 인터페이스를 사용했습니다.

fun interface ErrorHandler {
    fun handle(cause: Throwable): Any?
}

이 인터페이스는 handle 메소드 하나만을 가지고 있어, 람다 표현식으로 쉽게 구현할 수 있습니다.

3. BridgeRouter에 에러핸들러 통합

다음으로, BridgeRouter 클래스에 에러핸들러를 통합했습니다.

class BridgeRouter private constructor(
    private val objectMapper: ObjectMapper = jacksonObjectMapper(),
    private val logger: Logger = getLogger(BridgeRouter::class.java),
    private val routeTree: RouteNode = RouteNode(),
    private val decoratedService: BridgeService,
    private val errorHandlers: List<ErrorHandler> = emptyList(),
) {
    // ... 기존 코드 ...

    suspend fun routingRequest(
        pathAndQueryString: String,
        method: MethodType,
        headers: Map<String, String> = emptyMap(),
        body: Any? = null,
    ): String = try {
        // ... 기존 라우팅 로직 ...
    } catch (throwable: Throwable) {
        val actualException = when (throwable) {
            is InvocationTargetException -> throwable.targetException
            else -> throwable
        }
        logger.error("Routing error: ${actualException.message}", actualException)
        val results = errorHandlers.mapNotNull { it.handle(actualException) }
        if (results.isEmpty()) "500" else results.first()
            .serializeToJson(objectMapper)
    }
}

이 구현에서 주목할 점은 다음과 같습니다:

  1. 여러 개의 에러핸들러를 등록할 수 있습니다.
  2. 등록된 순서대로 에러핸들러를 실행합니다.
  3. 첫 번째로 null이 아닌 결과를 반환하는 핸들러의 결과를 사용합니다.
  4. 모든 핸들러가 null을 반환하면 기본적으로 "500" 에러를 반환합니다.

여기서 InvocationTargetException은 리플렉션을 사용하여 메서드를 호출할 때 발생할 수 있는 예외입니다. 이 예외는 실제 발생한 예외를 감싸고 있어, 우리는 targetException을 통해 원본 예외에 접근합니다. InvocationTargetException에 대한 자세한 설명은 이 링크를 참고하시기 바랍니다.

4. 에러핸들러 등록 방법

에러핸들러를 등록하는 방법은 BridgeRouter.Builder에 추가했습니다:

class Builder {
    // ... 기존 코드 ...

    fun registerErrorHandler(errorHandler: ErrorHandler): Builder {
        errorHandlers.add(errorHandler)
        logger.debug("ErrorHandler registered: $errorHandler")
        return this
    }

    fun registerAllErrorHandlers(errorHandlers: List<ErrorHandler>): Builder {
        this.errorHandlers.addAll(errorHandlers)
        logger.debug("ErrorHandlers registered: {}", errorHandlers)
        return this
    }

    // ... 기존 코드 ...
}

이렇게 하면 사용자가 쉽게 커스텀 에러핸들러를 등록할 수 있습니다.

5. 사용 예시

에러핸들러의 사용 예시는 다음과 같습니다:

val serviceExceptionHandler = ErrorHandler { throwable ->
    when (throwable) {
        is TestServiceException -> {
            ApiCommonResponse(
                status = -1,
                message = "TestServiceException occurred",
                data = ErrorData(throwable),
            )
        }
        is TestServiceException2 -> {
            ApiCommonResponse(
                status = -2,
                message = "TestServiceException2 occurred",
                data = ErrorData(throwable),
            )
        }
        else -> null
    }
}

val universalExceptionHandler = ErrorHandler { throwable ->
    ApiCommonResponse(
        status = -99,
        message = throwable.message ?: "Unknown error",
        data = ErrorData(throwable),
    )
}

val router = BridgeRouter.builder().apply {
    registerErrorHandler(serviceExceptionHandler)
    registerErrorHandler(universalExceptionHandler)
}.build()

이 예시에서는 두 개의 에러핸들러를 등록했습니다. 첫 번째 핸들러는 특정 예외를 처리하고, 두 번째 핸들러는 모든 예외를 처리합니다.

6. 마치며

에러핸들러를 구현하면서 느낀 점은 다음과 같습니다:

  1. 유연성의 중요성: 다양한 상황에 대응할 수 있는 유연한 설계가 중요합니다.
  2. 추상화의 중요성: 간단한 인터페이스로 복잡한 에러 처리 로직을 추상화할 수 있었습니다.

이번 에러핸들러 구현을 통해 Bridge-Api 라이브러리의 안정성과 사용성이 꽤나 향상되었다고 생각합니다.

다음 글에서는 Bridge-Api의 또 다른 중요한 기능인 데코레이터 패턴을 활용한 서비스 인터셉터 구현에 대해 다루어보도록 하겠습니다.

+ 라이브러리 레포지토리 : https://github.com/shiniseong/bridge-api