개발 가이드 백엔드 예외 처리 전략
최종 수정:

예외 처리 전략

전역 예외 처리, Slack 알람 연동, 에러 응답 형식

예외 처리 전략

기본 원칙

  • API 응답에 스택트레이스 노출 금지
  • 에러 응답에 내부 구현 정보 (클래스명, SQL 등) 노출 금지
  • 예외 발생 시 적절한 HTTP 상태코드 사용
  • 전역 예외는 GlobalExceptionHandler에서 일관 처리
  • ERROR 레벨 예외 발생 시 Slack 알람 자동 발송

GlobalExceptionHandler

// com.scraping.agent.global.exception.GlobalExceptionHandler
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {

    private final SlackService slackService;

    // 비즈니스 예외 (400)
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ApiResponse<Void>> handleIllegalArgument(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body(ApiResponse.fail(e.getMessage()));
    }

    // 서버 상태 오류 (500) — Slack 알람 발송
    @ExceptionHandler(IllegalStateException.class)
    public ResponseEntity<ApiResponse<Void>> handleIllegalState(
            IllegalStateException e, HttpServletRequest request) {
        slackService.sendError(request.getRequestURI(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.fail(e.getMessage()));
    }

    // 기타 서버 오류 (500) — Slack 알람 발송
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleGeneral(
            Exception e, HttpServletRequest request) {
        slackService.sendError(request.getRequestURI(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.fail("서버 오류가 발생했습니다."));
    }
}

Slack 알람 연동

GlobalExceptionHandler에서 SlackService.sendError()를 호출하여 500 에러 발생 시 Slack 채널에 자동 알림이 발송된다.

*[500 에러]* 서버 오류 발생
• 경로: `/api/v1/posts`
• 예외: `NullPointerException`
• 메시지: Cannot invoke method getId() on null object

슬랙 알람이 발송되는 시점:

  • IllegalStateException 발생 (서버 상태 오류)
  • Exception 기반 일반 서버 오류
  • SlackEventListener를 통한 서버 시작/종료 이벤트
// 배치 실패 시 직접 호출
slackService.sendBatchFail("DigestScheduler", exception);

검증 실패 처리

MethodArgumentNotValidException을 핸들러에 추가하여 @Valid 검증 실패를 처리한다.

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidation(MethodArgumentNotValidException e) {
    String message = e.getBindingResult().getFieldErrors().stream()
            .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
            .collect(Collectors.joining(", "));
    return ResponseEntity.badRequest().body(ApiResponse.fail(message));
}

사용자 정의 예외

// 도메인별 예외 클래스 — domain/{도메인}/ 패키지에 위치
public class PostNotFoundException extends RuntimeException {
    public PostNotFoundException(Long id) {
        super("포스트를 찾을 수 없습니다: " + id);
    }
}

public class NewsCollectException extends RuntimeException {
    public NewsCollectException(String source, Throwable cause) {
        super("뉴스 수집 실패: " + source, cause);
    }
}

Service에서 사용:

public PostDetailRes getPost(Long id) {
    return postRepository.findById(id)
            .map(PostDetailRes::from)
            .orElseThrow(() -> new IllegalArgumentException("포스트를 찾을 수 없습니다: " + id));
}

null 반환 금지

// 잘못된 예 — null 반환
public PostDetailRes getPost(Long id) {
    return postRepository.findById(id)
            .map(PostDetailRes::from)
            .orElse(null);   // 금지
}

// 올바른 예 — 예외로 처리
public PostDetailRes getPost(Long id) {
    return postRepository.findById(id)
            .map(PostDetailRes::from)
            .orElseThrow(() -> new IllegalArgumentException("포스트를 찾을 수 없습니다: " + id));
}

// 또는 Optional 반환
public Optional<PostDetailRes> findPost(Long id) {
    return postRepository.findById(id).map(PostDetailRes::from);
}

Thymeleaf 에러 페이지

templates/
└── error/
    ├── 400.html    # Bad Request
    ├── 403.html    # Forbidden
    ├── 404.html    # Not Found
    └── 500.html    # Internal Server Error

운영 환경 설정

# application-prod.yml
server:
  error:
    include-stacktrace: never    # 스택트레이스 응답 포함 금지
    include-message: never       # 예외 메시지 응답 포함 금지
    include-binding-errors: never

에러 응답 형식

// 400 Bad Request (입력값 오류)
{
    "success": false,
    "data": null,
    "error": "title: 제목은 필수입니다."
}

// 500 Internal Server Error
{
    "success": false,
    "data": null,
    "error": "서버 오류가 발생했습니다."
}

체크리스트

  • [ ] @RestControllerAdvice 전역 예외 처리 확인
  • [ ] 500 에러 시 SlackService.sendError() 호출 확인
  • [ ] API 응답에 스택트레이스 미포함
  • [ ] null 반환 없음 (Optional 또는 예외 처리)
  • [ ] 운영 환경 include-stacktrace: never 설정 확인
AI 문서 검색

현재 페이지 내용을 기반으로 질문하세요.