농산물 직거래 플랫폼, 중고 경매 플랫폼, 그리고 현재 진행중인 구미 페이먼츠 프로젝트에서도 꾸준히 개선해보려고 노력하고 있는 예외 처리에 대해 고민했던 내용들을 작성해 보려고 한다.
해당 프로젝트에서 예외를 처리했던 방식은 다음과 같다.

@RestControllerAdvice
@RequiredArgsConstructor
public class ExControllerAdvice {
private final ValidatorMessageUtils validatorMessageUtils;
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public ErrorRes BindException(BindException ex) {
BindingResult bindingResult = ex.getBindingResult();
return new ErrorRes(HttpStatus.BAD_REQUEST.value(), validatorMessageUtils.getValidationMessage(bindingResult));
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(FileSaveException.class)
public ErrorRes FileSaveException(FileSaveException ex) {
return new ErrorRes(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(LoginAuthenticationException.class)
public ErrorRes loginAuthExHandle(LoginAuthenticationException ex) {
return new ErrorRes(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
}
...
}
해당 프로젝트에서는 이전 프로젝트의 4가지 문제들을 개선해보려고 했다.
@Getter
@AllArgsConstructor
public enum ChatErrorCode implements ErrorCode{
CHAT_ROOM_DUPLICATED(HttpStatus.BAD_REQUEST, "채팅방이 이미 존재합니다."),
CHAT_ROOM_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 채팅방을 찾을 수 없습니다."),
INVALID_ROOM_ID(HttpStatus.BAD_REQUEST, "잘못된 채팅방 아이디 입니다.");
private final HttpStatus status;
private final String message;
}
@Getter
@AllArgsConstructor
public enum UserErrorCode implements ErrorCode {
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 사용자를 찾을 수 없습니다."),
INVALID_USER(HttpStatus.BAD_REQUEST, "올바르지 않은 사용자입니다."),
DUPLICATE_USER(HttpStatus.CONFLICT, "사용자가 이미 존재합니다."),
WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
EMAIL_AUTH_FAIL(HttpStatus.BAD_REQUEST, "이메일 인증 실패"),
SELLER_NOT_BIDDING(HttpStatus.BAD_REQUEST, "판매자는 입찰할 수 없습니다.");
private final HttpStatus status;
private final String message;
}
...
@Getter
@AllArgsConstructor
@Builder
public class ErrorRes {
private final HttpStatus status;
private final String code;
private final String msg;
public ErrorRes(ErrorCode errorCode) {
this.status = errorCode.getStatus();
this.code = errorCode.name();
this.msg = errorCode.getMessage();
}
public static ResponseEntity<ErrorRes> error(CustomException e) {
return ResponseEntity
.status(e.getErrorCode().getStatus())
.body(ErrorRes.builder()
.status(e.getErrorCode().getStatus())
.code(e.getErrorCode().name())
.msg(e.getErrorCode().getMessage())
.build());
}
// 예외 생성
memberRepository.findByLoginId(loginId)
.orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND));
// controller advice
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ErrorRes> handleCustomException(CustomException e) {
return ErrorRes.error(e);
}
결론적으로 예외 클래스가 너무 많아지는 점은 해결되고, controller advice에서 처리하는 예외도 대부분 하나의 exceptionhandler에서 처리하게 되었다.
또한 각 도메인 별 에러 코드 클래스에 들어가보면 어떤 도메인에서 어떤 예외가 발생하는지 알 수 있게 되었다.
해당 프로젝트에서도 마찬가지로 이전 프로젝트에서 고민했던 내용들을 개선해보려고 했다.
public class DuplicateSystemException extends RuntimeException implements SystemException {

public enum BusinessErrorCode implements ErrorCode{
NOT_FOUND,
TRY_AGAIN,
INVALID_STATUS,
TIMEOUT,
...
}
throw new SignupAcceptTimeoutException("만료 시간이 지났습니다.");@ExceptionHandler(value = SignupAcceptTimeoutException.class)
public ResponseEntity<ExceptionResponse> signupAcceptTimeoutExceptionHandler(SignupAcceptTimeoutException e) {
return ExceptionResponse.of(BusinessErrorCode.TIMEOUT, HttpStatus.BAD_REQUEST, e.getMessage());
}프로젝트들을 거치면서 예외 처리 방식에 대해 고민하고 개선하려고 여러 시도를 해보고 있다.
아직 프로젝트가 진행중이고, 개선한 방식에도 문제가 있을 가능성이 많다고 생각한다...
꾸준히 고민해볼 예정이다.