Controller에 단일책임원칙(SRP) 보장하기 (Exception 편)

영석·2025년 1월 6일

Spring

목록 보기
2/4
post-thumbnail

🧐 단일책임원칙(SRP)란?

SOLID 원칙 중 가장 첫번째 단일책임원칙

  • 하나의 클래스는 하나의 역할만 수행해야 한다.
  • 변경 사유는 하나여야 한다.

클래스가 너무 많은 책임을 가지면 코드가 복잡해지고 수정하기 어려워질뿐더러
하나의 책임을 변경할 때 다른 책임이 변경될 수 있다.

즉 Controller 자신의 본 책임, 요청과 응답을 처리하는 역할만 가지는 게 가장 좋은 구조이다.
가끔 공부를 하다 보면 Controller 내부에 예외처리코드를 넣은 코드를 보게 된다.

물론 작동은 잘 될 것이다, 하지만 Controller가 많아지고 내부 함수 또한 많아진다면
중복코드발생 및 수정할 때도 많은 시간이 걸릴 것이다...

📚 Controller에서 Exception코드 분리하기

  • @RestControllerAdvice + @ExceptionHandler 사용하기

@RestControllerAdvice

  • 컨트롤러 전역에서 발생하는 예외를 처리하기 위한 어노테이션이다.
  • AOP를 이용해서 존재하는 모든 Controller에 발생하는 에러를 캐치하여 공통으로 처리할 수 있게 도와준다.
  • 객체를 반환할 때 사용하는 특징이 있다.

@ExceptionHandler 란?

  • @Controller 어노테이션이 들어간 Bean에서 발생하는 예외를 캐치하여 Exception처리하는 역할을 한다.
  • 클래스 범위에서 작동하는 예외처리 메커니즘을 가진다, 즉 다른 컨트롤러에서 발생하는 에러는 처리하지 않습니다.
  • AOP를 사용하지 않고, HandlerExceptionResolver를 이용하여 예외처리 한다.

위 두가지 어노테이션을 같은 클래스에서 사용한다면 모든 Controller에서 발생하는 에러를 공통 로직에서 처리가능하다.

📚 예제코드

  • 먼저 UserController.java에 있는 예외처리 코드를 제거하고, GlobalExceptionHandler.java
    클래스를 만들어 UserController 뿐만 아닌 다른 Controller의 에러도 처리하려고 한다.

🌱 GlobalExceptionHandler.java

  • 저번 관통프로젝트에서 리펙토링을 진행하면서 만든 ExceptionHandler 코드임.
@RestControllerAdvice // 전역 Controller 에러 캐치
public class GlobalExceptionHandler {
	
    	// MethodArgumentNotValidException에 대한 에러 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
	
        // BindException에 대한 에러 처리
    @ExceptionHandler(BindException.class)
    protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
    
	    // ConstraintViolationException에 대한 에러 처리
    @ExceptionHandler(ConstraintViolationException.class)
    protected ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_INPUT_VALUE, e.getConstraintViolations());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
	
	    // MissingServletRequestParameterException에 대한 에러 처리
    @ExceptionHandler(MissingServletRequestParameterException.class)
    protected ResponseEntity<ErrorResponse> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.MISSING_REQUEST_PARAMS, e);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

	    // NullPointerException에 대한 에러 처리
    @ExceptionHandler(NullPointerException.class)
    protected ResponseEntity<ErrorResponse> handleNullPointerException(NullPointerException e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.NULL_POINT, e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }
	
    	// 나머지 에러처리
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  • 에러 응답에 일관성을 부여하기 위해서 ErrorResponse나 CustomErrorCode 등 사용하는 것을 권장한다.

🥑 기대효과

  • Controller와 예외처리코드를 분리하여 Controller의 본연의 책임에 집중할 수 있게 한다.
  • Controller에 대한 예외처리를 전역에서 공통코드로 처리하기 때문에 유지보수성이 향상되며 중복코드 감소한다.
profile
느리게 갱신되는 개발실력 - >_0

0개의 댓글