[Reach Rich 개발기] Custom Exception, Handler 만들기

wannabeking·2023년 3월 12일
0

Reach Rich 개발기

목록 보기
6/10

이전 시간에 로그인 관련 기능들을 개발했습니다.

Filter 단에서 세션 인가에 성공하지 못하면 Filter Chain에 등록해준 Denied Handler에 의해 Error Response가 나가겠지만, Spring 내부에선 단순 RuntimeException을 던져줬기 때문에 500 에러가 발생할 것입니다.

Exception이 발생할 수 있는 모든 부분에서 try-catch를 사용하게 되면 코드가 지저분해져 가독성이 낮아지므로, @RestControllerAdvice 어노테이션을 사용하여 Exception Handler를 붙여보겠습니다.



Custom Exception 생성

Exception을 핸들링하기 위해서는 어떠한 예외에서 어떠한 응답을 보낼지 결정해야합니다.

만약 발생할 수 있는 모든 종류의 에러별로 하나씩 Exception을 커스텀하여 생성한다면, 수많은 class 파일을 생성하거나 코드의 양이 방대해질 것입니다.

따라서 저는 하나의 Custom Exception을 생성하고 Error Code를 필드로 가져 enum으로 Error Code 값을 하나의 파일에서 관리하여 이점을 챙겨보겠습니다.


ErrorCode

@RequiredArgsConstructor
@Getter
public enum ErrorCode {

    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버에 예상치 못한 에러가 발생했습니다."),
    LOGIN_DENIED(HttpStatus.UNAUTHORIZED, "이메일 혹은 비밀번호가 일치하지 않습니다.");

    private final HttpStatus httpStatus;
    private final String message;
}

ErrorCode는 HTTP 상태 코드와 메시지를 담는 것으로 구현하겠습니다.

만약 새로운 에러에 대응해야 한다면, 단순히 enum에 값을 추가하면 될 것입니다.


CustomException

@RequiredArgsConstructor
@Getter
public class CustomException extends RuntimeException {

    private final ErrorCode errorCode;
}

ErrorCode를 enum으로 관리하기 때문에 우리는 각 상황에 맞는 에러코드를 파라미터로 CustomException을 던져주면 됩니다.

따라서 수 많은 Exception을 커스텀하여 작성할 필요가 없어졌습니다!


ErrorResponse

@RequiredArgsConstructor
@Getter
public class ErrorResponse {

    private final String message;
}

우리는 클라이언트에 적당한 HTTP 상태 코드와 에러 메시지를 응답해야하므로 간단한 Response DTO를 만들어줍니다.



Exception Handler

GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    private ResponseEntity<ErrorResponse> handleLoginDeniedException(CustomException e) {
        return handleExceptionInternal(e.getErrorCode());
    }

    @ExceptionHandler(Exception.class)
    private ResponseEntity<ErrorResponse> handleException(Exception e) {
        return handleExceptionInternal(ErrorCode.INTERNAL_SERVER_ERROR);
    }

    private ResponseEntity<ErrorResponse> handleExceptionInternal(ErrorCode errorCode) {
        ErrorResponse errorResponse = new ErrorResponse(errorCode.getMessage());
        return ResponseEntity.status(errorCode.getHttpStatus()).body(errorResponse);
    }
}

예외 처리는 위와 같이 작성했습니다.

만약 예외가 발생한다면,
CustomException이 가지고 있는 ErrorCode에서 httpStatus, message를 꺼내어 응답을 생성해 클라이언트에 보내게 됩니다.



테스트

구현이 끝났으니 로그인 API를 통해 테스트해봅니다.


UserService

    @Transactional(readOnly = true)
    public String login(HttpSession session, LoginDto loginDto) {
        User user = userRepository.findByEmail(loginDto.getEmail())
            .orElseThrow(() -> new CustomException(ErrorCode.LOGIN_DENIED))
            .toDomain();

        if (!user.isPasswordMatch(passwordEncoder, loginDto.getPassword())) {
            throw new CustomException(ErrorCode.LOGIN_DENIED);
        }

        session.setAttribute(LOGIN_USER, user);
        return session.getId();
    }

미리 만들어놓은 LOGIN_DENIED 에러코드로 CustomException을 생성하여 던져주면...

클라이언트는 설정한 에러코드에 맞는 HTTP 상태 코드와 메시지를 받을 수 있습니다.

만약 다른 종류의 에러를 핸들링해야 된다면, 해당 에러에 맞는 에러코드를 추가하는 방식으로 관리하면 되므로 개발의 편의성이 증가했습니다! 👍



profile
내일은 개발왕 😎

0개의 댓글