예외 계층 구조 설계하기
의외로 무언가를 만들 때 어디에 만들지? 라는 생각을 하게 된단 말이지~
그래서 넘어가려고 했지만 나에게는 필요할 것 같아서 적으며 공부해본다
- 예외 클래스 설계법
- 도메인별 예외 분리 기준
- 에러 코드 체계화
- 유지보수, 확장을 고려한 전략
예외 계층 구조
애플리케이션 내에서 발생할 수 있는 다양한 오류 상황을 의미 단위로 구분해서 설계한 예외 클래스의 구조
이걸 하면 무엇이 좋은가??
유지보수성 향상 / 책임의 분리 / 사용자경험 개선 과 같은 장점이 있다고 함
보통 src - exception - BaseException / ErrorCode / ValidationException / ResourceNotFoundException 형태로 설계함
BaseException : 모든 예외의 부모 클래스, 예외계의 Object라고 생각하면 편할듯
ErrorCode : 에러 코드와 메시지를 정의하는 곳(Enum 타입으로 정의해두는게 좋음)
ValidationException : 입력값 검증 실패 예외
ResourceNotFoundException : 리소스 찾을 수 없음 예외
예시
BaseException, abstract class
// 모든 커스텀 예외가 상속받는 추상 클래스 BaseException
public abstract class BaseException extends RuntimeException { Runtime을 상속받는다
private final ErrorCode errorCode;
public BaseException(ErrorCode errorCode) { // Setter
super(errorCode.getMessage()); // 조상의 생성자를 불러옴
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() { // Getter
return errorCode;
}
}
ErrorCode, enum타입
public enum ErrorCode { // enum타입
INVALID_INPUT("E001", "입력값이 유효하지 않습니다.");
UER_NOT_FOUND("E002","사용자를 찾을 수 없습니다.");
PERMISSION_DENIED("E003","권한이 없습니다.");
private final String code;
private final String message;
ErrorCode(String code, String message) { // setter
this.code = code;
this.message = message;
}
public String getCode() { // getter
return code;
}
public String getMessage() {
return message;
}
}
ValidationException, class - 입력값 검증 실패에 대한 예외
public class ValidationException extends BaseException { // BaseException 상속
public ValidationException(ErrorCode errorCode) {
super(errorCode); // public BaseException(ErrorCode errorCode) 을 불러온다
}
}
ResourceNotFoundException, class - 리소스를 찾을 수 없는 경우에 발생하는 예외를 다룸
public class ResourceNotFoundException extends BaseException {
public ResourceNotFoundException(ErrorCode errorCode) {
super(errorCode);
}
}
* 예외 클래스에 HttpStatus를 포함해서 ControllerAdvice에서 응답 변환이 용이하도록 설계하는게 좋다고 함
* 개발 초기에는 예외의 종류가 많진 않아도 이 형태, 틀을 갖추고 시작하면 확장에 유리하다
도메인별 예외 분리 전략
왜 도메인별로 예외를 분리하는가?
솔직히 코드 작성하고 클래스 만들고 파일 만들고 하다가 도메인별로 뭉쳐있는걸 보면 진짜 한숨이 다 나온다. 뭐가 이렇게 많아?
이걸 폴더별로 분류해서 가지런히 정돈해두면 깔끔하고.. 음.. 보기 좋다.. 흠..
따라서 나는 왠만하면 도메인별로 폴더를 구분해서 쓰는게 좋다고 본다.
근데 exception은 또 exception 안에 폴더를 도메인별로 분류해놓네?
src/
└── exception/
├── common/
│ └── BaseException.java
├── user/
│ ├── UserNotFoundException.java
│ └── DuplicateEmailException.java
└── auth/
├── TokenExpiredException.java
└── UnauthorizedAccessException.java
exception 안에 common, user, auth 폴더가 있고 common은 모든 예외의 상위 클래스인 BaseException이 있다.
user폴더에는 user에 쓰이는 예외가, auth에는 auth에 쓰이는 예외파일이 있다.
여기에 enum파일인 ErrorCode도 생각해야 하는데, ErrorCode는 모든 예외에 사용하니까? 왠만하면 common폴더가 맞겠는데?
user폴더에 있는 예외들 예시
UserNotFoundException
public class UserNotFoundException extends BaseException {
public UserNotFoundException() {
super(ErrorCode.USER_NOT_FOUND); // enum에서 가져옴
}
}
UnauthorizedAccessException
public class UnauthorizedAccessException extends BaseException {
public UnauthorizedAccessException() {
super(ErrorCode.PERMISSION_DENIED);
}
}
여기서 super는 BaseException의 setter을 가져온다.
public BaseException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
BaseException의 setter는 또 super을 호출해서 자손 클래스로부터 받은 인자를 전달하는데,
여기서 호출하는 super는 RuntimeException의 생성자이다.
따라서 UserNotFoundException / UnautorizedAccessException - > BaseException - > RuntimeException으로 전달하는 셈.
이렇게 도메인 레이어에 따라 exception 디렉토리를 나누면 참 보기도 좋고 관리하기도 편리하다.
각 도메인 담당자가 예외를 독립적으로 관리할 수 있어 협업에 유리하다. 이거 매우 중요한 점임
에러 코드 체계화 전략
에러 코드는 왜 필요한가??
만약 400, 404가 내 웹사이트 창에 떴을때 "아~ 사용자 문제구나" 할 수 있고, 500같은게 뜨면 "아~ 서버 문제구나" 등 단번에 알 수 있다. 이렇게 에러 상황에 대해 빠르고 정확하게 식별하는 데 에러 코드가 필요하다.
우리가 커스텀한 에러 코드를 짜놓은다면 문제가 발생했을 때 빠르게 대처할 수 있겠지?
체계 구성 기준의 예시
| 도메인 Prefix | 상세코드 | 설명 |
| USR | 001 | 사용자가 없음 |
| AUT | 002 | 인증이 실패함 |
| VAL | 003 | 입력값 오류 |
위의 enum에 적은 001, 002, 003 이런것들이다.
public enum ErrorCode {
USR_001("USR_001","해당 사용자를 찾을 수 없음");
AUT_002("AUT_002","인증에 실패함");
VAL_003("VAL_003","입력값이 잘못됨");
}
* 에러 코드는 프론트, 외부 시스템과의 연동시에도 유용하게 사용됨
* 로그 수집 시스템? 에서 필터링 기준으로 사용 가능하다고 함
'Spring Boot' 카테고리의 다른 글
| 로깅 : SLF4J, Logback (5) | 2025.08.12 |
|---|---|
| 로깅 : 로깅(Logging)의 필요성에 대해 (2) | 2025.08.12 |
| 예외 처리 : Spring 예외 처리 아키텍처에 대해서 (9) | 2025.08.12 |
| 예외 처리 : 예외 처리의 필요성 (1) | 2025.08.12 |
| Spring 안정성 높이기 : 애플리케이션 안정성 (3) | 2025.08.11 |