이룸 프로젝트
[이룸] 전역 에러 처리 리팩토링
쿠마냥
2024. 3. 27. 14:42
전역 에러 처리 리팩토링을 진행한 이유
- 문제를 빠르게 파악하기 위해 적절한 HTTP 응답 코드를 전달하고자 했다.
- 전역 에러 처리 없이 구현하려 하자, 컨트롤러/서비스 단에서 모든 상황에 if문을 걸어 http 코드를 하나하나 전달해주어야 했다. 코드가 복잡해지고 유지보수성도 좋지 않았다. 또한 반복되는 에러(ex. 레포지토리에 해당 멤버가 없음)에 같은 코드를 계속 적어주어야 해서 중복 코드 또한 많았다.
- 따라서 에러 상황을 미리 정의하고 한 곳에서 처리하도록 리팩토링을 진행하였다.
전역 에러 처리는 어떻게 동작할까?
- 서비스단의 비즈니스 로직에서 에러 발생! (ex. 멤버를 찾을 수 없음)
- throw new EroomException(ErrorCode.NOT_FOUND_MEMBER);
- error code 클래스에 미리 정의한 NOT_FOUND_MEMBER 에러를 파라미터로 받는 exception을 던진다. - exception이 발생한 것을 감지한 Global ExceptionHandler 메서드를 가동시킨다.
- ErromException에서 error code를 추출한다.
- error code에 담긴 두 정보, code와 message를 담은 response 객체를 생성한다.
- error code의 http status 정보까지 모아모아서 response entity를 생성한 후 클라이언트에 반환한다.
전역 에러 처리를 위해서 사용한 4개의 클래스:
- ErrorCode - 에러 코드와 관련 메시지, HTTP 상태를 정의하는 열거형(enum) 클래스.
- EroomException - 애플리케이션에서 사용할 커스텀 예외 클래스.
- ExceptionResponse - 클라이언트에게 반환될 에러 응답의 형태를 정의하는 클래스.
- GlobalExceptionHandler - 전역 예외 처리를 담당하는 클래스.
ErrorCode.java
@Getter
@AllArgsConstructor
public enum ErrorCode {
NOT_VALID_FORMAT(HttpStatus.BAD_REQUEST, "NOT_VALID_FORMAT", "지정된 양식을 사용해주세요."),
NOT_VALID_ACCESS(HttpStatus.BAD_REQUEST,"NOT_VALID_ACCESS","접근 권한이 없습니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "SERVER_001", "내부 서버 오류입니다."),
DUPLICATED_EMAIL(HttpStatus.BAD_REQUEST, "MEMBER_001", "중복된 이메일입니다."),
DUPLICATED_NICKNAME(HttpStatus.BAD_REQUEST, "MEMBER_002", "이미 사용 중인 닉네임입니다."),
NOT_VALID_PASSWORD(HttpStatus.BAD_REQUEST, "MEMBER_003", "비밀번호를 다시 확인해주세요."),
NOT_FOUND_MEMBER(HttpStatus.NOT_FOUND, "MEMBER_004", "찾을 수 없는 회원입니다."),
VERIFICATION_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "VERIFICATION_001", "인증 시간이 초과되었습니다."),
VERIFICATION_CODE_NOT_FOUND(HttpStatus.NOT_FOUND, "VERIFICATION_002", "인증 코드를 찾을 수 없습니다."),
NOT_MATCH_REFRESHTOKEN(HttpStatus.NOT_ACCEPTABLE, "MEMBER_005", "Refresh Token이 일치하지 않습니다."),
ACCESSTOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "JWT_001", "Access Token이 존재하지 않습니다."),
INVALID_ACCESSTOKEN(HttpStatus.BAD_REQUEST, "JWT_002", "Access Token이 유효하지 않습니다."),
EXPIRATION_ACCESSTOKEN(HttpStatus.UNAUTHORIZED, "JWT_003", "Access Token이 만료되었습니다"),
ACCESSTOKEN_NOT_SUPPORT(HttpStatus.UNAUTHORIZED, "JWT_004", "지원하지 않는 Access Token입니다"),
UNKNOWN_ACCESSTOKEN_ERROR(HttpStatus.UNAUTHORIZED, "JWT_005", "Access Token 에러입니다"),
REFRESHTOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "JWT_006", "Refresh Token이 존재하지 않습니다."),
INVALID_REFRESHTOKEN(HttpStatus.BAD_REQUEST, "JWT_007", "Refresh Token이 유효하지 않습니다."),
EXPIRATION_REFRESHTOKEN(HttpStatus.BAD_REQUEST, "JWT_008", "Refresh Token이 만료되었습니다"),
REFRESHTOKEN_NOT_SUPPORT(HttpStatus.UNAUTHORIZED, "JWT_009", "지원하지 않는 Refresh Token입니다"),
UNKNOWN_REFRESHTOKEN_ERROR(HttpStatus.UNAUTHORIZED, "JWT_010", "Refresh Token 에러입니다"),
NOT_AUTHORIZED_MEMBER(HttpStatus.BAD_REQUEST, "JWT_011", "인가되지 않은 사용자입니다."),
UNKNOWN_TOKEN_ERROR(HttpStatus.BAD_REQUEST, "JWT_012", "알 수 없는 토큰 에러입니다"),
SOCIAL_LOGIN_ERROR(HttpStatus.UNAUTHORIZED, "SOCIAL_001", "소셜 로그인 오류입니다"),
DUPLICATED_SOCIAL_EMAIL(HttpStatus.NOT_ACCEPTABLE, "SOCIAL_002", "이미 가입된 계정입니다"),
NOT_FOUND_NOTIFICATION(HttpStatus.NOT_FOUND,"NOTIFICATION_001","찾을 수 없는 알림입니다.");
private final HttpStatus httpStatus;
private final String code;
private final String message;
}
ErromException.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class EroomException extends RuntimeException{
private ErrorCode errorCode;
}
ExceptionResponse.java
@Getter
@RequiredArgsConstructor
public class ExceptionResponse {
private final String code;
private final String message;
}
GlobalExceptionHandler.java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EroomException.class)
public ResponseEntity<ExceptionResponse> handleEroomException(EroomException e) {
ErrorCode errorCode = e.getErrorCode();
ExceptionResponse response = new ExceptionResponse(errorCode.getCode(), errorCode.getMessage());
return new ResponseEntity<>(response, errorCode.getHttpStatus());
}
}