포스트

Spring 애플리케이션에서 예외를 설계하는 방법

예외를 어떻게 봐야 하는가

예외는 “실패” 그 자체라기보다 “정상 흐름에서 벗어난 상황”을 표현하는 장치다. 여기서 중요한 것은 모든 실패를 예외로 취급하지 않는 것이다.

예를 들어 사용자가 로그인 화면에서 비밀번호를 틀리는 일은 충분히 예상 가능한 입력 실패다. 반면, 의도하지 않은 상태 값 변조나 시스템 계약을 깨는 요청은 예외에 가깝다.

즉, 예외 설계의 출발점은 “어떤 실패를 시스템이 비정상으로 간주할 것인가”를 정하는 일이다.

예외를 분기 처리 수단으로 남용하지 말 것

다음과 같은 코드는 흔하지만 좋지 않다.

1
2
3
4
5
try {
    policy.validate(request);
} catch (Exception e) {
    return false;
}

이 방식은 정상 흐름과 실패 흐름을 모두 흐리게 만든다. 예외는 정말 예외적인 상황에 쓰고, 예상 가능한 검증 실패는 명시적 반환이나 도메인 검증 로직으로 다루는 편이 낫다.

커스텀 예외는 왜 필요한가

프레임워크 예외만 그대로 쓰기 시작하면 애플리케이션의 의도가 드러나지 않는다.

예를 들어 아래 둘은 의미가 다르다.

  • 주문이 존재하지 않음
  • 현재 상태에서는 주문 취소가 허용되지 않음

둘 다 4xx일 수는 있지만, 도메인 의미와 운영 대응은 다르다. 따라서 예외는 비즈니스 의미 단위로 나누는 것이 좋다.

기본 설계 원칙

1. 예외 이름은 상황을 설명해야 한다

  • OrderNotFoundException
  • InvalidOrderStateException
  • PaymentAlreadyApprovedException

2. 필요하면 원인 예외를 함께 보존한다

1
2
3
4
5
public class ExternalApiException extends RuntimeException {
    public ExternalApiException(String message, Throwable cause) {
        super(message, cause);
    }
}

Root cause를 잃어버리면 운영 시 추적이 어려워진다.

3. 예외 계층은 응답 정책과 연결되어야 한다

예외를 너무 세밀하게 쪼개더라도 결국 HTTP 응답이 모두 같다면 계층이 과도할 수 있다. 반대로 처리 방식이 다르다면 분리할 가치가 있다.

상태 코드와의 매핑

예외 설계는 결국 HTTP 상태 코드와 연결된다.

  • 잘못된 입력: 400
  • 인증 실패: 401
  • 권한 없음: 403
  • 리소스 없음: 404
  • 상태 충돌: 409
  • 서버 오류: 500

핵심은 “상태 코드를 먼저 고르고 예외를 맞추는 것”이 아니라, 도메인 실패를 정의한 뒤 적절한 응답으로 매핑하는 것이다.

실무에서 자주 놓치는 부분

  • printStackTrace()에 의존함
  • 내부 테이블 구조나 SQL 메시지를 외부에 노출함
  • 모든 예외를 한 핸들러에서 뭉개 버림
  • 예외가 너무 많아져도 기준이 없음

예외 수가 많은 것 자체는 문제는 아니다. 다만 분류 기준이 명확해야 한다.

좋은 예외 설계의 기준

  • 도메인 의미가 드러난다.
  • 응답 정책과 자연스럽게 연결된다.
  • 운영 로그에서 원인을 추적할 수 있다.
  • 정상 흐름 제어를 예외에 의존하지 않는다.

정리

예외는 “실패를 감추는 도구”가 아니라 “실패를 정교하게 드러내는 도구”다. 스프링 애플리케이션에서는 예외를 잘 설계할수록 컨트롤러, 서비스, 전역 핸들러의 책임이 명확해지고 운영 시점의 추적성도 좋아진다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

댓글

아직 댓글이 없습니다