이번 친구 요청 기능을 구현하면서, 발생 가능한 다양한 예외 상황에 대해 전략적인 예외 처리 구조를 설계했습니다. 이 구조는 다음과 같은 흐름으로 구성되어 있습니다:
ErrorCode
Enum먼저, 발생 가능한 모든 예외 상황을 ErrorCode
라는 Enum으로 정의했습니다.
이 Enum에는 에러 메시지, HTTP 상태 코드가 함께 포함되어 있어 에러 정보의 일관성을 유지할 수 있습니다.
예시:
ALREADY_REQUESTED("이미 친구 요청을 보냈습니다", HttpStatus.CONFLICT),
REQUEST_NOT_FOUND("존재하지 않는 친구 요청입니다", HttpStatus.NOT_FOUND),
INVALID_CANCEL("취소할 수 없는 친구 요청 상태입니다", HttpStatus.BAD_REQUEST)
이 방식은 하드코딩된 메시지를 피하고, 중앙에서 에러 메시지를 관리할 수 있도록 해줍니다. 또한 예외처리를 추가하기 편하면서 재사용성을 증가시킬 수 있다는 장점이 있습니다.
CustomException
비즈니스 로직(Service) 내에서 문제가 발생했을 때는 CustomException
을 발생시킵니다.
이 예외는 ErrorCode
를 포함하고 있어서, 어떤 예외가 어떤 이유로 발생했는지를 정확히 표현할 수 있습니다.
if (exists) {
throw new CustomException(ErrorCode.ALREADY_REQUESTED);
}
GlobalExceptionHandler
스프링의 @RestControllerAdvice
를 활용해, 전체 애플리케이션에서 발생한 예외를 한 곳에서 통합 처리합니다.
핸들러 예시:
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponseDto> handleCustomException(CustomException ex, HttpServletRequest request) {
return ResponseEntity
.status(ex.getErrorCode().getStatus())
.body(ErrorResponseDto.from(ex.getErrorCode(), request.getRequestURI()));
}
여기서 핵심은, 예외가 발생했을 때 ResponseEntity로 구조화된 Dto 객체를 반환한다는 점입니다.
ErrorResponseDto
우리는 단순히 Map<String, String>
형태가 아닌, ErrorResponseDto
라는 전용 Dto를 통해 응답을 보냅니다.
이 Dto는 다음과 같은 정보를 포함합니다:
status
: HTTP 상태 코드error
: 상태 이름 (예: BAD_REQUEST)message
: 사용자에게 보여질 메시지path
: 어떤 요청에서 오류가 발생했는지timestamp
: 에러 발생 시간fieldErrors
: 유효성 검사 실패 시 필드별 상세 에러 리스트{
"timestamp": "2025-04-10T15:09:16.523464",
"status": 400,
"error": "BAD_REQUEST",
"message": "잘못된 입력값입니다",
"path": "/api/users",
"fieldErrors": [
{
"field": "password",
"message": "비밀번호는 영문 대소문자, 숫자, 특수문자를 포함해 8자 이상 20자 이하이어야 합니다."
},
{
"field": "email",
"message": "이메일 형식이 아닙니다"
}
]
}
Dto
로 예외 응답을 처리했는가?마지막으로, 예외 응답을 단순 Map이 아닌 Dto를 사용한 이유는 다음과 같습니다:
요약 멘트:
"이렇게
ErrorCode
,CustomException
,GlobalExceptionHandler
, 그리고ErrorResponseDto
를 통해 예외를 체계적으로 처리함으로써, 사용자에게는 명확한 피드백을, 개발자에게는 유지보수성과 확장성을 제공할 수 있었습니다."