CommonMessageException 정의하기

Enum에 OK("1000", "성공") 이런 식으로 정의해놓는건... 분명 좋은 방식이긴 하지만,

```java

throw new MyException(dr.getServiceName() + "/" + dr.getCorpName());

```

 

이런 케이스가 커버가 안됨. 특정 메시지나 데이터를 클라이언트 쪽에 내려주고 싶을 때.

다건 업데이트 같은 작업 도중 실패가 발생한 경우, 어느 항목에서 실패가 발생했는지를 클라이언트 쪽에 내려주어야 하는 경우들이 있음.

해결책은? 이런 경우에 사용할 일반적인 Exception을 하나 정의하는 것이 낫다.

 

```java

public class CommonMessageException extends RuntimeException {
    private String externalMessage;

    public boolean isIgnorable() { return this.getCause() == null; }

    ...

```

  • 이런 식으로 외부 메시지를 별도로 받아서 내려주도록 구성하면, 내부에 찍는 RuntimeException.message와 externalMessage를 구분할 수 있음.
  • 특정 에러는 로그를 찍도록 하고, 나머지는 안찍도록 구분하고 싶은 경우, cause에 담긴 것이 있느냐 없느냐로 구분할 수 있음.
  • ResultCode Enum field 자체에 ignorable을 가지고 있는 것은 좋지 않다.
    • "미지원 항목" 응답 코드 처럼 ignorable일 때도, not ignorable일 때도 있을 수 있는 경우 응답 코드를 2개 만들어야 해서 나쁘다.
    • 서비스 단에서 Exception을 던질 때 이 에러를 무시할지, 말지를 결정할 수 있도록 하는 것은 좋으나, 굳이 무시 가능/불가능으로 이분법 구분 하지 말고, LEVEL 자체를 명시하는 것이 낫다. (하단 참조)
  • 여기서는 Message필드만 있고 Code 필드는 따로 안썼는데, 필요한 경우에는
    • Enum을 사용하여 Code, Message를 묶고, 생성자 오버로딩으로 Enum을 받도록 구성할 수 있음.
      • 생성자에서 이 Enum을 풀어서 code, message 변수에 할당.
    • 내부에서는 String code, String message로 가지고 있으므로, business layer에서 특정 문자열을 넘기도록 할 수 있음.
      • Enum만 사용하면 dr.getServiceName() 이런 문자열을 business layer에서 넘겨 받는 것이 불가능하니까... 내부 타입은 String이어야함.

 

isIgnorable 통해 이분법으로 구분하지 말고, 아예 Level을 받아버리는 것이 낫다.

```kt

class CommonException(
  val level: Level,
  override val message: String,
  override val cause: Throwable? = null
) : RuntimeException(message, cause) {}

```

```kt

@ExceptionHandler(CommonException::class)
fun handleCommonException(e: CommonException): ResponseEntity<Any> {
  when (e.level) {
    Level.TRACE -> log().trace("### message=${e.message}", e.cause)
    Level.DEBUG -> log().debug("### message=${e.message}", e.cause)
    Level.INFO -> log().info("### message=${e.message}", e.cause)
    Level.WARN -> log().warn("### message=${e.message}", e.cause)
    Level.ERROR -> log().error("### message=${e.message}", e.cause)
  }

  

  if (e.level.toInt() > Level.INFO.toInt()) {

    notifier.notify(e.message)

  }


  return ResponseEntity
    .status(HttpStatus.INTERNAL_SERVER_ERROR)
    .contentType(MediaType.APPLICATION_JSON)
    .body("[${e.level}] ${e.message}");
}

```

  • 분명 이건 오류가 맞긴 맞으니 ExceptionHandler에서 처리하고 싶은데, 심각하지 않은 오류라서 Level.INFO로 기록하고 Notify 하고 싶지 않을 수도 있다.
  • 이런 세부적인 처리를 하기 위한 별도 CommonException을 정의하고, 기타 Exception(e.g., WebClientRequestException) 발생 시 CommonException으로 변환해서 던지는 방법을 사용.
  • WebClientRequestException 전체에 대해 일괄 처리를 적용하는 것 보다 나은 경우가 많음.
  • CommonException은 다음과 같은 상황에서 의미가 있음.
    • 위 예제는 단순 log, notify만 하기 때문에 각 사용처에서 직접 찍어주어도 되겠으나 이 보다 처리가 복잡한 경우. (or 확장성 고려)
    • return...return 이 아니라 한 번에 GlobalExceptionHandler 까지 탈출하는 용도 (그 밖에 orElseThrow 등)

 

CommonMessageException에 data: T 필드를 추가하면 더 좋지 않을까?

이는 불가능하다.

throw Exception을 return 처럼 정보 반환 용도로 사용해서는 안된다