728x90
320x100
🎯 오늘의 목표
- 글로벌 예외처리 및 API 응답 포맷 통일
⚙️ 진행한 작업
- 글로벌 예외처리
- API 응답 포맷 통일
🛠️ 개발내용
📌 공통 응답 구조 ApiResponse<T> 설계 (ApiResponse.java)
package ddururi.bookbookclub.global.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class ApiResponse<T> {
private boolean success;
private T data;
private String message;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, data, null);
}
public static <T> ApiResponse<T> success(T data, String message) {
return new ApiResponse<>(true, data, message);
}
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, null, message);
}
}
→ API 응답을 성공/실패 관계없이 동일한 구조로 반환되도록 함.
📌 ErrorCode enum 클래스 정의 (ErrorCode.java)
package ddururi.bookbookclub.global.exception;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
EMAIL_NOT_VERIFIED("EMAIL_NOT_VERIFIED", "이메일 인증이 완료되지 않았습니다."),
USER_WITHDRAWN("USER_WITHDRAWN", "탈퇴한 사용자입니다."),
INVALID_PASSWORD("INVALID_PASSWORD", "비밀번호가 일치하지 않습니다."),
REJOIN_RESTRICTED("REJOIN_RESTRICTED", "탈퇴 후 6개월 이내에는 재가입할 수 없습니다."),
USER_NOT_FOUND("USER_NOT_FOUND", "사용자를 찾을 수 없습니다."),
DUPLICATE_EMAIL("DUPLICATE_EMAIL", "이미 가입된 이메일입니다."),
DUPLICATE_NICKNAME("DUPLICATE_NICKNAME", "이미 사용 중인 닉네임입니다."),
INTERNAL_SERVER_ERROR("INTERNAL_SERVER_ERROR", "알 수 없는 서버 오류가 발생했습니다.");
private final String code;
private final String message;
}
📌 @RestControllerAdvice 기반의 글로벌 예외 처리기 작성 (GlobalExceptionHandler.java)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EmailNotVerifiedException.class)
public ResponseEntity<ApiResponse<Void>> handleEmailNotVerified(EmailNotVerifiedException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(UserWithdrawnException.class)
public ResponseEntity<ApiResponse<Void>> handleWithdrawn(UserWithdrawnException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(InvalidPasswordException.class)
public ResponseEntity<ApiResponse<Void>> handleInvalidPassword(InvalidPasswordException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(RejoinRestrictionException.class)
public ResponseEntity<ApiResponse<Void>> handleRejoinRestricted(RejoinRestrictionException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<?>> handleAllException(Exception ex) {
ex.printStackTrace(); // 운영 환경에서는 로거로 처리
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.fail(ErrorCode.INTERNAL_SERVER_ERROR));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleUserNotFound(UserNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ApiResponse<Void>> handleDuplicateEmail(DuplicateEmailException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.fail(e.getErrorCode()));
}
@ExceptionHandler(DuplicateNicknameException.class)
public ResponseEntity<ApiResponse<Void>> handleDuplicateNickname(DuplicateNicknameException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponse.fail(e.getErrorCode()));
}
}
예외 발생 시 ApiResponse.fail() 형태로 자동 변환하여 통일된 응답 제공
📌 상황에 맞는 커스텀 예외 정의 (Ex. EmailNotVerifiedException.java)
@Getter
public class EmailNotVerifiedException extends RuntimeException {
private final ErrorCode errorCode;
public EmailNotVerifiedException() {
super(ErrorCode.EMAIL_NOT_VERIFIED.getMessage());
this.errorCode = ErrorCode.EMAIL_NOT_VERIFIED;
}
}
→ 기존 IllegalStateException, IllegalArgumentException을 도메인 기반 커스텀 예외로 치환하여 예외 의미를 더 명확하게 전달
📌 커스텀 예외 적용 (Ex. UserService.java)
private void validateEmailVerification(String email) {
if (!emailVerificationService.isEmailVerified(email)) {
throw new EmailNotVerifiedException();
}
}
✅ 장점
항목 | 설명 |
✅ 응답 일관성 확보 | 모든 API 응답이 같은 구조 (success, data, message)로 반환돼, 프론트 개발자 입장에서 처리하기 쉬움 |
✅ 중복 코드 제거 | 컨트롤러마다 try-catch 하지 않아도 됨 |
✅ 유지보수 용이성 | 새로운 예외 발생 시 handler만 추가하면 됨 |
✅ 도메인 기반 예외 분리 | UserWithdrawnException, InvalidPasswordException 같은 구체적이고 읽기 쉬운 구조 |
✅ 실서버 대응 용이 | Exception.class에서 로그 출력 처리 → 원인 추적 가능 |
728x90
320x100
'💻 뚝딱뚝딱 > 북북클럽' 카테고리의 다른 글
[개발일지 #016] 북(Book) 도메인 개발 및 단위 테스트 (0) | 2025.04.29 |
---|---|
[개발일지 #015] 이메일 인증 실패 시도 횟수 제한 기능 (0) | 2025.04.25 |
[개발일지 #013] 회원 프로필 사진 등록 기능 구현 (0) | 2025.04.24 |
[개발일지 #012] Oauth 로그인 구현 (네이버) (0) | 2025.04.24 |
[개발일지 #011] Oauth 로그인 구현 (구글) (0) | 2025.04.24 |