부트캠프 과제 中 트러블슈팅 과정 작성 글
일정 관리 앱 서버 ver.2
※ 이용 방법만 필요하면 목차에서 [구현 과정] 클릭
💡트러블 슈팅
1. 문제
상황마다 Exception
을 만들고, Global Exception Handler
를 사용하다 Exception Class를 여러 개 만들어야 하는 문제가 생겼다. 상황에 맞는 정확한 이름이 담긴 Exception을 만드는 것이 좋지만, 대부분 접근 권한이나 path입력, null입력 등 유효성 문제에 대한 예외 처리였기 때문에 그 의도는 모두 같았다. 발생되는 원인만 다른 것 뿐.
2. 원인
문제가 발생될 시점에서 if
로 확인하고, throw new
로 상황에 맞는 Exception을 만들어 발생시키고 있었지만, 최종 응답은 같은 모양이었다. 응답에 필요한 ErrorResponseDto
를 만들어 해당 Dto 형태로 응답하고 있었다.
응답 형태도 같은 모양이고, 그 문제만 클라이언트에게 전달하면 되는 것이니 하나의 Exception안에서 문제를 구분시켜주면 될 것 같았다.
3. 해결 방안
Exception을 하나로 줄이고, 인자로 문제에 맞는 상수를 넣어줘 한번에 해결하는 방식을 사용하기로 했다.
문제에 대한 상태 코드와 전달 메세지를 담은 Enum
을 만들어, Exception 파라미터에 이 Enum의 값을 전달한다. 그리고 Global Exception Handler
에서 Exception이 발생하면, Enum 값에 있는 상태 코드와 메세지를 ResponseDto
에 담아서 전달한다.
이 방법을 사용하면 클라이언트에게 상태코드와 문제 발생 메세지를 한번에 전달할 수 있고, 여러 Exception을 만들지 않아도 '유효성 문제'를 해결하기 위한 Exception만 사용하면 간단하게 해결이 된다.
🛠️ 구현 과정
1. 응답 형태를 담을 Dto 생성
@Getter
public class ErrorResponseDto {
private HttpStatus httpStatus;
private String message;
public ErrorResponseDto(ExceptionCode exceptionCode) {
this.httpStatus = exceptionCode.getStatus();
this.message = exceptionCode.getMessage();
}
}
2. Enum 작성
public enum ExceptionCode {
USER_NOT_FOUND(HttpStatus.NOT_FOUND,"USER_NOT_FOUND"),
SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "SCHEDULE_NOT_FOUND"),
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMENT_NOT_FOUND"),
DUPLICATE_USER_EMAIL(HttpStatus.BAD_REQUEST, "DUPLICATE_USER_EMAIL"),
SESSION_NOT_VALID(HttpStatus.FORBIDDEN, "SESSION_NOT_VALID"),
EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, "EMAIL_NOT_FOUND"),
PASSWORD_NOT_MATCH(HttpStatus.UNAUTHORIZED, "PASSWORD_NOT_MATCH"),
PAGE_NOT_POSITIVE(HttpStatus.NOT_FOUND,"PAGE_NOT_POSITIVE"),
PAGE_OVER(HttpStatus.NOT_FOUND,"PAGE_OVER");
private final HttpStatus status;
private final String message;
ExceptionCode(HttpStatus status, String message) {
this.status = status;
this.message = message;
}
public HttpStatus getStatus(){
return status;
}
public String getMessage(){
return MessageUtil.getMessage(message);
}
}
상황에 맞는 HttpStatus
와 String message
를 담아 Enum
을 생성하였다.
이제 Exception 생성자를 이용해 이 ExceptionCode<E>
를 넣어서 예외처리를 진행하려고 한다.
여기에서 작성된 message는 i18n
을 적용하기 위해 properties에 작성하였다.
3. resources에 properties 작성
적용하는 방법 확인
src/main/resources
에 messages
폴더를 생성해 그 안에 properties를 작성해주었다.Accept-Language
에 맞는 언어를 응답해주기 위한 것으로 제일 기본인 en과 ko만 생성하였다.
USER_NOT_FOUND=해당 id를 찾을 수 없습니다.
SCHEDULE_NOT_FOUND=해당되는 id의 글을 찾을 수 없습니다.
COMMENT_NOT_FOUND=해당되는 id의 댓글을 찾을 수 없습니다.
DUPLICATE_USER_EMAIL=이미 존재하는 email입니다. 다른 email을 입력해주세요.
SESSION_NOT_VALID=잘못된 접근입니다. 다시 확인해주세요.
EMAIL_NOT_FOUND=존재하지 않는 email 입니다.
PASSWORD_NOT_MATCH=비밀번호가 일치하지 않습니다.
PAGE_NOT_POSITIVE=존재하지 않는 페이지 입니다. (0 이하는 올 수 없습니다.)
PAGE_OVER=존재하지 않는 페이지 입니다.
USER_NOT_FOUND=The ID cannot be found.
SCHEDULE_NOT_FOUND=The SCHEDULE ID cannot be found.
COMMENT_NOT_FOUND=The COMMENT ID cannot be found.
DUPLICATE_USER_EMAIL=This email already exists. Please enter a different email.
SESSION_NOT_VALID=Invalid Access. Please check again.
EMAIL_NOT_FOUND=Does not exist EMAIL
PASSWORD_NOT_MATCH=PASSWORD does not match.
PAGE_NOT_POSITIVE=This page doesn't exist. (Value never below Zero)
PAGE_OVER=This page doesn't exist.
왜 저기만 색이 다르지? 😅
Config
를 작성하면 개인 설정에 따라서 메세지가 다르게 전달된다.
Enum
의 String message
에서는 이 값에 맞는 메세지가 각각 출력될 것이다.
4. Enum을 담을 Exception 생성
@Getter
public class ValidException extends RuntimeException {
private final ExceptionCode exceptionCode;
public ValidException(ExceptionCode exceptionCode) {
this.exceptionCode = exceptionCode;
}
}
생성자 파라미터에서 Enum으로 생성한 ExceptionCode
를 넘겨주었다.
이제 예외가 발생할 때마다 상황에 맞는 상수를 전달하면 된다.
사용 예시
public void validateSessionUser(Long sessionUserId, Long requestUserId) {
if (!sessionUserId.equals(requestUserId)) {
throw new ValidException(ExceptionCode.SESSION_NOT_VALID);
}
}
위 상황은 Session에 저장된 user 정보가 요청한 user와 맞지 않을 경우, 해당 user정보에 접근할 수 없다는 메세지를 넘겨주기 위한 SESSION_NOT VALID
를 이용하는 모습이다. 접근하는 session이 유효하지 않을 때 사용한다.
5. Exception Handler 이용
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<List<FiledErrorResponseDto>> filedErrorException(MethodArgumentNotValidException e) {
...
}
@ExceptionHandler(ValidException.class)
public ResponseEntity<ErrorResponseDto> violationException(ValidException e) {
return new ResponseEntity<>(new ErrorResponseDto(e.getExceptionCode()), e.getExceptionCode().getStatus());
}
ExceptionHandler
를 만들 때는 클래스에 @ControllerAdvice
를 부여한다.
해당 프로젝트의 Controller에 @RestController
를 사용했기 때문에 좀 더 정확한 @RestControllerAdvice
를 사용했다.
그리고 어떤 예외를 핸들링할지 해당 Exception
을 명시해줘야 한다.@ExceptionHandelr(Exception.class)
를 이용해 수행할 로직을 작성하면 된다.
위에는 Filed Error
를 받아서 처리할 로직이고, 아래에 작성한 것이 위에서 만든 ValidException
을 처리할 로직이다.
DTO에서는 HttpStatus와 String message를 응답한다.
ValidException에서는 생성자 파라미터로 ExceptionCode<E>를 받고 있기 때문에 get()
으로 e.getExceptionCode()를 그대로 넘겨줄 수 있다. DTO에서는 이 ExceptionCode를 받아 HttpStatus와 String에 해당되는 값을 대입한다.
// ValidException
public ValidException(ExceptionCode exceptionCode) {
this.exceptionCode = exceptionCode;
}
// ErrorResponseDto
public ErrorResponseDto(ExceptionCode exceptionCode) {
this.httpStatus = exceptionCode.getStatus();
this.message = exceptionCode.getMessage();
}
//ExceptionHandler
new ErrorResponseDto(e.getExceptionCode())
예외가 발생하면서 Enum을 받아주고, 그 값이 Dto에 전달되며 상태코드와 메세지를 응답할 수 있게 된다.
그리고 ResponseEntity
를 이용했으니 상태코드를 넘겨줄때는 e.getExceptionCode().getStatus()
를 이용할 수 있다.
Enum에서 한번에 HttpStatus와 함께 상태를 전달하고, 클라이언트에게도 어떤 상태인지 그 메세지를 함꼐 전달한다.
✅ 결과
Enum을 이용해 상황에 맞는 코드와 메세지를 만들어 작성했고, Exception 생성자 파라미터로 Enum을 받는다. 그리고 ExceptionHandler를 이용해 Exception을 처리하고 상태코드와 함께 메세지를 응답해준다.
여러 개의 예외 클래스를 만들지 않고 유효성 검사를 하는 상황에서 상수를 이용해 작성할 수 있다.
'Framework > Spring' 카테고리의 다른 글
[Spring] Enum에 i18n 적용하는 방법 (0) | 2025.02.12 |
---|---|
[Spring] 의존 관계 주입(DI) & 의존 관계 검색(DL) (0) | 2024.12.17 |
[Spring] 싱글톤 레지스트리(Singleton Registry)란? (0) | 2024.12.09 |
[토비의스프링] 1.5 스프링의 IoC (0) | 2024.12.03 |
[토비의스프링] 1.4 제어의 역전(IoC) (1) | 2024.11.29 |